mittens_engine/engine/ecs/component/
music_note.rs1use super::{Component, ComponentRef};
2use crate::engine::ecs::{ComponentId, World};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
5#[serde(rename_all = "lowercase")]
6pub(crate) enum NotePitch {
7 A,
8 B,
9 C,
10 D,
11 E,
12 F,
13 G,
14}
15
16#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
17pub struct MusicNote {
18 duration: f32,
19 #[serde(default = "MusicNote::default_velocity")]
20 velocity: f32,
21 pitch: NotePitch,
22 octave: u16,
23}
24
25impl Default for MusicNote {
26 fn default() -> Self {
27 Self {
28 duration: 0.25,
29 velocity: 1.0,
30 pitch: NotePitch::A,
31 octave: 4,
32 }
33 }
34}
35
36impl MusicNote {
37 fn default_velocity() -> f32 {
38 1.0
39 }
40
41 pub fn duration_beats(&self) -> f32 {
42 self.duration
43 }
44
45 pub fn velocity(&self) -> f32 {
46 self.velocity
47 }
48
49 pub fn octave(&self) -> u16 {
50 self.octave
51 }
52
53 pub fn pitch_name(&self) -> &'static str {
54 match self.pitch {
55 NotePitch::A => "a",
56 NotePitch::B => "b",
57 NotePitch::C => "c",
58 NotePitch::D => "d",
59 NotePitch::E => "e",
60 NotePitch::F => "f",
61 NotePitch::G => "g",
62 }
63 }
64
65 pub fn with_duration_beats(mut self, duration_beats: f32) -> Self {
66 self.duration = duration_beats;
67 self
68 }
69
70 pub fn with_velocity(mut self, velocity: f32) -> Self {
71 self.velocity = if velocity.is_finite() {
72 velocity.max(0.0)
73 } else {
74 1.0
75 };
76 self
77 }
78
79 pub fn with_octave(mut self, octave: u16) -> Self {
80 self.octave = octave;
81 self
82 }
83
84 pub fn a(octave: u16, duration_beats: f32) -> Self {
85 Self {
86 duration: duration_beats,
87 velocity: 1.0,
88 pitch: NotePitch::A,
89 octave,
90 }
91 }
92
93 pub fn b(octave: u16, duration_beats: f32) -> Self {
94 Self {
95 duration: duration_beats,
96 velocity: 1.0,
97 pitch: NotePitch::B,
98 octave,
99 }
100 }
101
102 pub fn c(octave: u16, duration_beats: f32) -> Self {
103 Self {
104 duration: duration_beats,
105 velocity: 1.0,
106 pitch: NotePitch::C,
107 octave,
108 }
109 }
110
111 pub fn d(octave: u16, duration_beats: f32) -> Self {
112 Self {
113 duration: duration_beats,
114 velocity: 1.0,
115 pitch: NotePitch::D,
116 octave,
117 }
118 }
119
120 pub fn e(octave: u16, duration_beats: f32) -> Self {
121 Self {
122 duration: duration_beats,
123 velocity: 1.0,
124 pitch: NotePitch::E,
125 octave,
126 }
127 }
128
129 pub fn f(octave: u16, duration_beats: f32) -> Self {
130 Self {
131 duration: duration_beats,
132 velocity: 1.0,
133 pitch: NotePitch::F,
134 octave,
135 }
136 }
137
138 pub fn g(octave: u16, duration_beats: f32) -> Self {
139 Self {
140 duration: duration_beats,
141 velocity: 1.0,
142 pitch: NotePitch::G,
143 octave,
144 }
145 }
146
147 pub(crate) fn from_pitch(duration_beats: f32, pitch: NotePitch, octave: u16) -> Self {
148 Self {
149 duration: duration_beats,
150 velocity: 1.0,
151 pitch,
152 octave,
153 }
154 }
155
156 pub(crate) fn pitch(&self) -> NotePitch {
157 self.pitch
158 }
159}
160
161#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
162pub struct MusicNoteComponent {
163 pub note: MusicNote,
164
165 #[serde(skip)]
171 pub target_source: Option<ComponentRef>,
172
173 #[serde(skip)]
177 pub target_resolved: Option<ComponentId>,
178
179 #[serde(default)]
182 pub scheduled_beat: Option<f64>,
183
184 #[serde(default)]
187 pub play_on_attach: bool,
188
189 #[serde(skip)]
190 component: Option<ComponentId>,
191}
192
193impl MusicNoteComponent {
194 pub fn new(note: MusicNote) -> Self {
195 Self {
196 note,
197 target_source: None,
198 target_resolved: None,
199 scheduled_beat: None,
200 play_on_attach: false,
201 component: None,
202 }
203 }
204
205 pub fn from_note(note: MusicNote) -> Self {
206 Self::new(note)
207 }
208
209 pub fn with_target_source(mut self, source: ComponentRef) -> Self {
210 self.target_source = Some(source);
211 self
212 }
213
214 pub fn with_scheduled_beat(mut self, beat: f64) -> Self {
215 self.scheduled_beat = Some(beat);
216 self
217 }
218
219 pub fn with_play_on_attach(mut self, on: bool) -> Self {
220 self.play_on_attach = on;
221 self
222 }
223
224 pub fn id(&self) -> Option<ComponentId> {
225 self.component
226 }
227
228 pub fn resolve_target(&mut self, world: &mut World) -> Option<ComponentId> {
234 if self.target_resolved.is_some() {
235 return self.target_resolved;
236 }
237 if let Some(src) = self.target_source.as_ref() {
238 let resolved = match src {
239 ComponentRef::Guid(uuid) => world.component_id_by_guid(*uuid),
240 ComponentRef::Query(selector) => {
241 let roots: Vec<ComponentId> = world
242 .all_components()
243 .filter(|&cid| world.parent_of(cid).is_none())
244 .collect();
245 roots
246 .into_iter()
247 .find_map(|root| world.find_component(root, selector))
248 }
249 };
250 self.target_resolved = resolved;
251 if resolved.is_some() {
252 return resolved;
253 }
254 }
255 None
256 }
257}
258
259impl Default for MusicNoteComponent {
260 fn default() -> Self {
261 Self::new(MusicNote::default())
262 }
263}
264
265impl Component for MusicNoteComponent {
266 fn set_id(&mut self, component: ComponentId) {
267 self.component = Some(component);
268 }
269
270 fn name(&self) -> &'static str {
271 "music_note"
272 }
273
274 fn as_any(&self) -> &dyn std::any::Any {
275 self
276 }
277
278 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
279 self
280 }
281
282 fn init(&mut self, emit: &mut dyn crate::engine::ecs::SignalEmitter, component: ComponentId) {
283 if !self.play_on_attach {
284 return;
285 }
286 let target = self.target_resolved.unwrap_or(component);
294 emit.push_intent_now(
295 component,
296 crate::engine::ecs::IntentValue::AudioSchedulePlay {
297 component_ids: vec![target],
298 beat_offset: 0.0,
299 beat_context: self.scheduled_beat,
300 note: Some(self.note),
301 gain: None,
302 rate: None,
303 duration: None,
304 },
305 );
306 }
307
308 fn to_mms_ast(
309 &self,
310 _world: &crate::engine::ecs::World,
311 ) -> crate::scripting::ast::ComponentExpression {
312 use crate::engine::ecs::component::ce_helpers::*;
313 let pitch = self.note.pitch_name();
314 let mut c = ce_call(
315 "MusicNote",
316 pitch,
317 vec![
318 num(self.note.octave() as f64),
319 num(self.note.duration_beats() as f64),
320 ],
321 );
322 if (self.note.velocity() - 1.0).abs() > f32::EPSILON {
323 c = c.with_call("velocity", vec![num(self.note.velocity() as f64)]);
324 }
325 if self.play_on_attach {
326 c = c.with_call("play_on_attach", vec![]);
327 }
328 if let Some(b) = self.scheduled_beat {
329 c = c.with_call("at_beat", vec![num(b)]);
330 }
331 c
332 }
333}