1use core::fmt;
12
13use bevy_ecs::prelude as ecs;
14#[cfg(not(feature = "std"))]
16#[allow(unused_imports)]
17use num_traits::float::Float as _;
18
19use crate::math::{PositiveSign, ZeroOne};
20use crate::transaction::{self, Equal, Transaction};
21use crate::universe;
22
23mod ambient;
26pub use ambient::{Ambient, Band, SpatialAmbient, Spectrum};
27
28#[derive(Clone, Debug, Eq, Hash, PartialEq, bevy_ecs::component::Component)]
35#[expect(clippy::module_name_repetitions)]
36#[non_exhaustive]
37pub struct SoundDef {
38 pub duration: ZeroOne<f32>,
40
41 pub frequency: PositiveSign<f32>,
43
44 pub amplitude: ZeroOne<f32>,
49 }
56
57impl SoundDef {
58 #[doc(hidden)] pub fn synthesize(&self, sample_rate: f32) -> impl Iterator<Item = [f32; 2]> {
60 let sample_count = (self.duration.into_inner() * sample_rate).round() as usize;
61 let sample_index_to_radians =
62 self.frequency.into_inner() * core::f32::consts::TAU / sample_rate;
63 let amplitude = self.amplitude.into_inner();
64
65 (0..sample_count).map(move |sample_index| {
66 let time_in_fraction = sample_index as f32 / (sample_count - 1).max(1) as f32;
67 let time_in_radians = sample_index as f32 * sample_index_to_radians;
68
69 let envelope: f32 = (1.0 - time_in_fraction).sqrt();
70
71 let wave: f32 = time_in_radians.sin() * amplitude;
72 [wave * envelope; 2]
73 })
74 }
75}
76
77universe::impl_universe_member_for_single_component_type!(SoundDef);
78
79impl universe::VisitHandles for SoundDef {
80 fn visit_handles(&self, _: &mut dyn universe::HandleVisitor) {
81 let Self {
82 duration: _,
83 frequency: _,
84 amplitude: _,
85 } = self;
86 }
87}
88
89#[derive(Clone, Debug, Eq, Hash, PartialEq)]
93pub struct DefTransaction {
94 old: Equal<SoundDef>,
95 new: Equal<SoundDef>,
96}
97
98impl transaction::Transactional for SoundDef {
99 type Transaction = DefTransaction;
100}
101
102impl DefTransaction {
103 pub fn expect(old: SoundDef) -> Self {
106 Self {
107 old: Equal(Some(old)),
108 new: Equal(None),
109 }
110 }
111
112 pub fn overwrite(new: SoundDef) -> Self {
114 Self {
115 old: Equal(None),
116 new: Equal(Some(new)),
117 }
118 }
119
120 pub fn replace(old: SoundDef, new: SoundDef) -> Self {
123 Self {
124 old: Equal(Some(old)),
125 new: Equal(Some(new)),
126 }
127 }
128}
129
130impl Transaction for DefTransaction {
131 type Target = SoundDef;
132 type CommitCheck = ();
133 type Context<'a> = universe::ReadTicket<'a>;
135 type Output = transaction::NoOutput;
136 type Mismatch = Mismatch;
137
138 fn check(
139 &self,
140 target: &SoundDef,
141 _read_ticket: universe::ReadTicket<'_>,
142 ) -> Result<Self::CommitCheck, Self::Mismatch> {
143 self.old.check(target).map_err(|_| Mismatch::Unexpected)
144 }
145
146 fn commit(
147 self,
148 target: &mut SoundDef,
149 (): Self::CommitCheck,
150 _outputs: &mut dyn FnMut(Self::Output),
151 ) -> Result<(), transaction::CommitError> {
152 if let Equal(Some(new)) = self.new {
153 *target = new;
154 }
159 Ok(())
160 }
161}
162
163impl universe::TransactionOnEcs for DefTransaction {
164 type WriteQueryData = &'static mut Self::Target;
165
166 fn check(
167 &self,
168 target: &SoundDef,
169 read_ticket: universe::ReadTicket<'_>,
170 ) -> Result<Self::CommitCheck, Self::Mismatch> {
171 Transaction::check(self, target, read_ticket)
172 }
173
174 fn commit(
175 self,
176 mut target: ecs::Mut<'_, SoundDef>,
177 check: Self::CommitCheck,
178 ) -> Result<(), transaction::CommitError> {
179 Transaction::commit(self, &mut *target, check, &mut transaction::no_outputs)
180 }
181}
182
183impl transaction::Merge for DefTransaction {
184 type MergeCheck = ();
185 type Conflict = Conflict;
186
187 fn check_merge(&self, other: &Self) -> Result<Self::MergeCheck, Self::Conflict> {
188 let conflict = Conflict {
189 old: self.old.check_merge(&other.old).is_err(),
190 new: self.new.check_merge(&other.new).is_err(),
191 };
192
193 if (conflict
194 != Conflict {
195 old: false,
196 new: false,
197 })
198 {
199 Err(conflict)
200 } else {
201 Ok(())
202 }
203 }
204
205 fn commit_merge(&mut self, other: Self, (): Self::MergeCheck) {
206 let Self { old, new } = self;
207 old.commit_merge(other.old, ());
208 new.commit_merge(other.new, ());
209 }
210}
211
212#[derive(Clone, Debug, Eq, PartialEq, displaydoc::Display)]
214#[non_exhaustive]
215pub enum Mismatch {
216 Unexpected,
218}
219
220#[derive(Clone, Copy, Debug, Eq, PartialEq)]
224#[non_exhaustive]
225pub struct Conflict {
226 pub(crate) old: bool,
228 pub(crate) new: bool,
230}
231
232impl core::error::Error for Mismatch {}
233impl core::error::Error for Conflict {}
234
235impl fmt::Display for Conflict {
236 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237 match *self {
238 Conflict {
239 old: true,
240 new: false,
241 } => write!(f, "different preconditions for SoundDef"),
242 Conflict {
243 old: false,
244 new: true,
245 } => write!(f, "cannot write different new values to the same SoundDef"),
246 Conflict {
247 old: true,
248 new: true,
249 } => write!(f, "different preconditions (with write)"),
250 Conflict {
251 old: false,
252 new: false,
253 } => unreachable!(),
254 }
255 }
256}