1mod message;
2use message::*;
3
4use koto::prelude::*;
5use koto::runtime::KList;
6use koto::runtime::KNumber;
7use koto::runtime::KMap;
8use koto::runtime::KValue;
9use koto::Error as RuntimeError;
10
11pub fn collect_list_of_midi_bytes_as_u8(
13 message: &KList,
14 error: &str,
15) -> std::result::Result<Vec<u8>, RuntimeError> {
16 let arguments = message
17 .data()
18 .iter()
19 .map(|v| match v {
20 KValue::Number(num) => match num {
21 KNumber::I64(midi_byte) if *midi_byte >= 0 && *midi_byte < 128 => Ok(*midi_byte as u8),
23 _ => runtime_error!(error),
24 },
25 _ => {
26 runtime_error!(error)
27 }
28 })
29 .collect::<std::result::Result<Vec<u8>, RuntimeError>>();
30 arguments
31}
32
33pub fn collect_list_of_u8(
34 message: &KList,
35 error: &str,
36) -> std::result::Result<Vec<u8>, RuntimeError> {
37 let arguments = message
38 .data()
39 .iter()
40 .map(|v| match v {
41 KValue::Number(num) => match num {
42 KNumber::I64(byte) if *byte >= 0 && *byte <= 255 => Ok(*byte as u8),
44 _ => runtime_error!(error),
45 },
46 _ => {
47 runtime_error!(error)
48 }
49 })
50 .collect::<std::result::Result<Vec<u8>, RuntimeError>>();
51 arguments
52}
53pub fn collect_list_of_u64(
54 message: &KList,
55 error: &str,
56) -> std::result::Result<Vec<u64>, RuntimeError> {
57 let arguments = message
58 .data()
59 .iter()
60 .map(|v| match v {
61 KValue::Number(num) => match num {
62 KNumber::I64(midi_byte) if *midi_byte >= 0 => Ok(*midi_byte as u64),
64 _ => runtime_error!(error),
65 },
66 _ => {
67 runtime_error!(error)
68 }
69 })
70 .collect::<std::result::Result<Vec<u64>, RuntimeError>>();
71 arguments
72}
73
74pub fn collect_list_of_value_list(
75 message: &KList,
76 error: &str,
77) -> std::result::Result<Vec<KList>, RuntimeError> {
78 let arguments = message
79 .data()
80 .iter()
81 .map(|v| match v {
82 KValue::List(list) => Ok(list.clone()),
83 _ => {
84 runtime_error!(error)
85 }
86 })
87 .collect::<std::result::Result<Vec<KList>, RuntimeError>>();
88 arguments
89}
90
91
92fn pascal_case_to_underscore_separated_literal(string_to_process: &str) -> std::string::String {
93 let mut literal = String::new();
94 for (i,ch) in string_to_process.chars().enumerate() {
95 if ch.is_uppercase() && i != 0 {
96 literal.push('_');
97 literal.push_str(&format!("{}",ch.to_lowercase())[..]);
98 continue;
99 }
100 else if ch.is_uppercase() {
101 literal.push_str(&format!("{}",ch.to_lowercase())[..]);
102 continue;
103 }
104 literal.push(ch);
105 }
106 literal
107}
108
109macro_rules! types {
110 ($map:ident,$($type_literal:literal),*) => {
111 $($map.insert($type_literal, $type_literal);)*
112 }
113}
114
115macro_rules! impl_pack {
116 ($map:ident, $message:ident) => {
117 $map.add_fn("pack", move |_| {
118 Ok(KValue::List(KList::from_slice(
119 &$message
120 .pack()
121 .into_iter()
122 .map(|byte| byte.into())
123 .collect::<Vec<KValue>>()[..],
124 )))
125 });
126 };
127}
128
129macro_rules! make_koto_message_constructor {
130 ($map:ident, $enum_key:ident, $category_literal:literal, $($field:ident),*, $error_literal:literal) => {
131 let name_literal = pascal_case_to_underscore_separated_literal(stringify!($enum_key));
132 $map.add_fn(&name_literal.clone(), move |ctx| {
133 let args = ctx.args();
134 if args.len() == 1 {
135 match args {
136 [KValue::List(message)] => {
137 if let Ok(arguments) = collect_list_of_u64(message, $error_literal) {
138 if let [$($field),*] = &arguments[..] {
139 let message_koto = KMap::new();
140 let message = <$enum_key>::new($(*$field),*);
142 message_koto.insert("type", name_literal.clone());
144 message_koto.insert("category", $category_literal);
145 $(
146 message_koto.insert(stringify!($field), message.$field());
148 )*
149 impl_pack!(message_koto, message);
150 Ok(KValue::Map(message_koto))
151 }
152 else {
153 runtime_error!($error_literal)
154 }
155 } else {
156 Ok(KValue::Null)
157 }
158 }
159 _ => runtime_error!($error_literal),
160 }
161 } else {
162 runtime_error!($error_literal)
163 }
164 })
165 };
166
167 ($map:ident, $enum_key:ty, $category_literal:literal, $error_literal:literal) => {
168 let name_literal = pascal_case_to_underscore_separated_literal(stringify!($enum_key));
169 $map.add_fn(&name_literal.clone(), move |ctx| {
170 let args = ctx.args();
171 if args.is_empty() {
172 let message_koto = KMap::new();
173 let message = <$enum_key>::default();
174 message_koto.insert("type", name_literal.clone());
175 message_koto.insert("category", $category_literal);
176 impl_pack!(message_koto, message);
177 Ok(KValue::Map(message_koto))
178 } else {
179 runtime_error!($error_literal)
180 }
181 })
182 }
183}
184
185
186macro_rules! make_koto_message {
187 ($map:ident, $message:ident, $name_literal:literal,$($field:ident),*) => {
188 $map.insert("type", $name_literal);
189 $(
190 $map.insert(stringify!($field),$message.$field());
191 )*
192 impl_pack!($map, $message);
193 }
194}
195
196
197pub fn make_module() -> KMap {
198 let module = KMap::new();
199 let types = KMap::new();
200
201 types!(
202 types,
203 "note_off",
204 "note_on",
205 "poly_after_touch",
206 "control_change",
207 "program_change",
208 "after_touch",
209 "pitch_bend",
210 "all_sound_off",
211 "reset_all_controllers",
212 "local_control",
213 "all_notes_off",
214 "omni_mode_off",
215 "omni_mode_on",
216 "mono_mode_on",
217 "poly_mode_on",
218 "system_exclusive",
219 "time_code_quarter_frame",
220 "song_position",
221 "song_select",
222 "tune_request",
223 "end_of_exclusive",
224 "timing_clock",
225 "start",
226 "continue",
227 "stop",
228 "active_sensing",
229 "reset",
230 "undefined",
231 "malformed"
232 );
233
234 let categories = KMap::new();
235
236 types!(
237 categories,
238 "channel_voice",
239 "channel_mode",
240 "system_common",
241 "system_realtime",
242 "unknown"
243 );
244
245 let message_constructors = KMap::new();
246
247 make_koto_message_constructor!(
248 message_constructors,
249 NoteOff,
250 "channel_voice",
251 note,
252 velocity,
253 channel,
254 "note_off requires a single list of exactly three integers as its argument"
255 );
256
257 make_koto_message_constructor!(
258 message_constructors,
259 NoteOn,
260 "channel_voice",
261 note,
262 velocity,
263 channel,
264 "note_on requires a single list of exactly three integers as its argument"
265 );
266
267 make_koto_message_constructor!(
268 message_constructors,
269 PolyAfterTouch,
270 "channel_voice",
271 note,
272 pressure,
273 channel,
274 "poly_after_touch requires a single list of exactly three integers as its argument"
275 );
276
277 make_koto_message_constructor!(
278 message_constructors,
279 ControlChange,
280 "channel_voice",
281 note,
282 value,
283 channel,
284 "control_change requires a single list of exactly three integers as its argument"
285 );
286
287 make_koto_message_constructor!(
288 message_constructors,
289 ProgramChange,
290 "channel_voice",
291 program,
292 channel,
293 "program_change requires a single list of exactly two positive integers as its argument"
294 );
295
296 make_koto_message_constructor!(
297 message_constructors,
298 AfterTouch,
299 "channel_voice",
300 pressure,
301 channel,
302 "after_touch requires a single list of exactly two positive integers as its argument"
303 );
304
305 make_koto_message_constructor!(
306 message_constructors,
307 PitchBend,
308 "channel_voice",
309 bend_amount,
310 channel,
311 "pitch_bend requires a single list of exactly two positive integers as its argument"
312 );
313
314 make_koto_message_constructor!(
315 message_constructors,
316 AllSoundOff,
317 "channel_mode",
318 value,
319 channel,
320 "all_sound_off requires a single list of exactly two positive integers as its argument"
321 );
322
323 make_koto_message_constructor!(
324 message_constructors,
325 ResetAllControllers,
326 "channel_mode",
327 value,
328 channel,
329 "reset_all_controllers requires a single list of exactly two positive integers as its argument"
330 );
331
332 make_koto_message_constructor!(
333 message_constructors,
334 LocalControl,
335 "channel_mode",
336 value,
337 channel,
338 "local_control requires a single list of exactly two positive integers as its argument"
339 );
340
341 make_koto_message_constructor!(
342 message_constructors,
343 AllNotesOff,
344 "channel_mode",
345 value,
346 channel,
347 "all_notes_off requires a single list of exactly two positive integers as its argument"
348 );
349
350 make_koto_message_constructor!(
351 message_constructors,
352 OmniModeOff,
353 "channel_mode",
354 value,
355 channel,
356 "omni_mode_off requires a single list of exactly two positive integers as its argument"
357 );
358
359 make_koto_message_constructor!(
360 message_constructors,
361 OmniModeOn,
362 "channel_mode",
363 value,
364 channel,
365 "omni_mode_on requires a single list of exactly two positive integers as its argument"
366 );
367
368 make_koto_message_constructor!(
369 message_constructors,
370 MonoModeOn,
371 "channel_mode",
372 value,
373 channel,
374 "mono_mode_on requires a single list of exactly two positive integers as its argument"
375 );
376
377 make_koto_message_constructor!(
378 message_constructors,
379 PolyModeOn,
380 "channel_mode",
381 value,
382 channel,
383 "poly_mode_on requires a single list of exactly two positive integers as its argument"
384 );
385
386 message_constructors.add_fn("system_exclusive", |ctx| {
389 let error_literal = "system_exclusive requires a list with single or 3 bytes for its first argument and a list with one or more bytes for its second argument";
390 let args = ctx.args();
391 if args.len() == 2 {
392 match args {
393 [KValue::List(message)] => {
394 if let Ok(arguments) = collect_list_of_value_list(message, error_literal) {
395 if let [manufacturer_id,message] = &arguments[..] {
396 match manufacturer_id.len() {
397 1 | 3 => {
398 match message.len() {
399 0 => runtime_error!(error_literal),
400 _ => {
401 if let Ok(m_id) = collect_list_of_u8(manufacturer_id, error_literal) {
402 if let Ok(data) = collect_list_of_u8(message, error_literal) {
403 let message_koto = KMap::new();
404 let message = SystemExclusive::new(&m_id[..], &data[..]);
405 message_koto.insert("type", "system_exclusive");
406 message_koto.insert("category", "system_common");
407 let m_id = m_id.iter().map(|&x| x.into()).collect::<Vec<KValue>>();
408 message_koto.insert("manufacturer_id", KValue::List(KList::from_slice(&m_id[..])));
409 impl_pack!(message_koto, message);
410 Ok(KValue::Map(message_koto))
411 }
412 else{
413 runtime_error!(error_literal)
414 }
415 }
416 else {
417 runtime_error!(error_literal)
418 }
419 }
420 }
421 }
422 _ => runtime_error!(error_literal)
423 }
424 }
425 else {
426 runtime_error!(error_literal)
427 }
428 } else {
429 Ok(KValue::Null)
430 }
431 }
432 _ => runtime_error!(error_literal),
433 }
434 } else {
435 runtime_error!(error_literal)
436 }
437 });
438
439 make_koto_message_constructor!(
442 message_constructors,
443 TimeCodeQuarterFrame,
444 "system_common",
445 message_type,
446 values,
447 "time_code_quarter_frame requires a single list of exactly two positive integers as its argument"
448 );
449
450 make_koto_message_constructor!(
451 message_constructors,
452 SongPosition,
453 "system_common",
454 midi_beats_elapsed,
455 "song_position requires a single list of exactly one positive integer as its argument"
456 );
457
458 make_koto_message_constructor!(
459 message_constructors,
460 SongSelect,
461 "system_common",
462 number,
463 "song_select requires a single list of exactly one positive integer as its argument"
464 );
465
466 make_koto_message_constructor!(
467 message_constructors,
468 TuneRequest,
469 "system_common",
470 "tune_request does not take any arguments"
471 );
472
473 make_koto_message_constructor!(
474 message_constructors,
475 EndOfExclusive,
476 "system_common",
477 "end_of_exclusive does not take any arguments"
478 );
479
480 make_koto_message_constructor!(
481 message_constructors,
482 TimingClock,
483 "system_realtime",
484 "timing_clock does not take any arguments"
485 );
486
487 make_koto_message_constructor!(
488 message_constructors,
489 Start,
490 "system_realtime",
491 "start does not take any arguments"
492 );
493
494 make_koto_message_constructor!(
495 message_constructors,
496 Continue,
497 "system_realtime",
498 "continue does not take any arguments"
499 );
500
501 make_koto_message_constructor!(
502 message_constructors,
503 Stop,
504 "system_realtime",
505 "stop does not take any arguments"
506 );
507
508 make_koto_message_constructor!(
509 message_constructors,
510 ActiveSensing,
511 "system_realtime",
512 "active_sensing does not take any arguments"
513 );
514
515 make_koto_message_constructor!(
516 message_constructors,
517 Reset,
518 "system_realtime",
519 "reset does not take any arguments"
520 );
521
522 module.add_fn("parse", |ctx| {
523 let args = ctx.args();
524 if args.len() == 1 {
525 match args {
526 [KValue::List(message)] => {
527 let message_koto = KMap::new();
528 if let Ok(midi_message) = collect_list_of_u8(
529 message,
530 "parse requires a single list of one or more positive integers as its argument",
531 ) {
532 let parsed = ParsedMessage::from(&midi_message[..]);
533 let message = parsed.message;
534
535 match message {
536 Message::NoteOn(_)
537 | Message::NoteOff(_)
538 | Message::ControlChange(_)
539 | Message::ProgramChange(_)
540 | Message::PitchBend(_)
541 | Message::AfterTouch(_)
542 | Message::PolyAfterTouch(_) => {
543 message_koto.insert("category", "channel_voice")
544 }
545 Message::AllSoundOff(_)
546 | Message::ResetAllControllers(_)
547 | Message::LocalControl(_)
548 | Message::AllNotesOff(_)
549 | Message::OmniModeOff(_)
550 | Message::OmniModeOn(_)
551 | Message::MonoModeOn(_)
552 | Message::PolyModeOn(_) => {
553 message_koto.insert("category", "channel_mode")
554 }
555 Message::SystemExclusive(_)
556 | Message::SongPosition(_)
557 | Message::SongSelect(_)
558 | Message::TuneRequest(_)
559 | Message::EndOfExclusive(_)
560 | Message::TimeCodeQuarterFrame(_) => {
561 message_koto.insert("category", "system_common")
562 }
563 Message::TimingClock(_)
564 | Message::Start(_)
565 | Message::Continue(_)
566 | Message::Stop(_)
567 | Message::ActiveSensing(_)
568 | Message::Reset(_) => {
569 message_koto.insert("category", "system_realtime")
570 }
571 Message::Undefined | Message::Malformed => {
572 message_koto.insert("category", "unknown")
573 }
574 };
575
576 match message {
577 Message::NoteOff(message) => {
578 make_koto_message!(message_koto, message, "note_off", note, velocity, channel);
579 }
580 Message::NoteOn(message) => {
581 make_koto_message!(message_koto, message, "note_on", note, velocity, channel);
582 }
583 Message::ControlChange(message) => {
584 make_koto_message!(message_koto, message, "control_change", note, value, channel);
585 }
586 Message::ProgramChange(message) => {
587 make_koto_message!(message_koto, message, "program_change", program, channel);
588 }
589
590 Message::AfterTouch(message) => {
591 make_koto_message!(message_koto, message, "after_touch", pressure, channel);
592 }
593 Message::PolyAfterTouch(message) => {
594 make_koto_message!(message_koto, message, "poly_after_touch", note, pressure, channel);
595 }
596 Message::PitchBend(message) => {
597 make_koto_message!(message_koto, message, "pitch_bend", bend_amount, channel);
598 }
599 Message::AllSoundOff(message) => {
600 make_koto_message!(message_koto, message, "all_sound_off", value, channel);
601 message_koto.insert("note", 120);
602 }
603 Message::ResetAllControllers(message) => {
604 make_koto_message!(message_koto, message, "reset_all_controllers", value, channel);
605 message_koto.insert("note", 121);
606 }
607 Message::LocalControl(message) => {
608 make_koto_message!(message_koto, message, "local_control", value, channel);
609 message_koto.insert("note", 122);
610 }
611 Message::AllNotesOff(message) => {
612 make_koto_message!(message_koto, message, "all_notes_off", value, channel);
613 message_koto.insert("note", 123);
614 }
615 Message::OmniModeOff(message) => {
616 make_koto_message!(message_koto, message, "omni_mode_off", value, channel);
617 message_koto.insert("note", 124);
618 }
619 Message::OmniModeOn(message) => {
620 make_koto_message!(message_koto, message, "omni_mode_on", value, channel);
621 message_koto.insert("note", 125);
622 }
623 Message::MonoModeOn(message) => {
624 make_koto_message!(message_koto, message, "mono_mode_on", value, channel);
625 message_koto.insert("note", 126);
626 }
627 Message::PolyModeOn(message) => {
628 make_koto_message!(message_koto, message, "poly_mode_on", value, channel);
629 message_koto.insert("note", 127);
630 }
631 Message::SystemExclusive(message) => {
632 message_koto.insert("type", "system_exclusive");
633 let m_id = message.manufacturer_id.iter().map(|&x| x.into()).collect::<Vec<KValue>>();
634 message_koto.insert("manufacturer_id", KValue::List(KList::from_slice(&m_id[..])));
635 impl_pack!(message_koto, message);
636 }
637 Message::SongPosition(message) => {
638 make_koto_message!(message_koto, message, "song_position", midi_beats_elapsed);
639 }
640 Message::SongSelect(message) => {
641 make_koto_message!(message_koto, message, "song_select", number);
642 }
643 Message::TuneRequest(message) => {
644 message_koto.insert("type", "tune_request");
645 impl_pack!(message_koto, message);
646 }
647 Message::EndOfExclusive(message) => {
648 message_koto.insert("type", "end_of_exclusive");
649 impl_pack!(message_koto, message);
650 }
651 Message::TimeCodeQuarterFrame(message) => {
652 make_koto_message!(message_koto, message, "time_code_quarter_frame", message_type, values);
653 }
654 Message::TimingClock(message) => {
655 message_koto.insert("type", "timing_clock");
656 impl_pack!(message_koto, message);
657 }
658 Message::Start(message) => {
659 message_koto.insert("type", "start");
660 impl_pack!(message_koto, message);
661 }
662 Message::Continue(message) => {
663 message_koto.insert("type", "continue");
664 impl_pack!(message_koto, message);
665 }
666 Message::Stop(message) => {
667 message_koto.insert("type", "stop");
668 impl_pack!(message_koto, message);
669 }
670 Message::ActiveSensing(message) => {
671 message_koto.insert("type", "active_sensing");
672 impl_pack!(message_koto, message);
673 }
674 Message::Reset(message) => {
675 message_koto.insert("type", "reset");
676 impl_pack!(message_koto, message);
677 }
678 Message::Undefined => {
679 message_koto.insert("type", "undefined");
680 }
681 Message::Malformed => {
682 message_koto.insert("type", "malformed");
683 }
684 }
685
686 Ok(KValue::Map(message_koto))
687 } else {
688 message_koto.insert("type", "malformed");
689 message_koto.insert("category", "unknown");
690 Ok(KValue::Map(message_koto))
692 }
693 }
694 _ => runtime_error!(
695 "parse requires a single list of one or more positive integers as its argument"
696 ),
697 }
698 } else {
699 runtime_error!("parse requires a single list of one or more positive integers as its argument")
700 }
701 });
702
703 module.insert("types", types);
704 module.insert("categories", categories);
705 module.insert("message", message_constructors);
706 module
707}
708
709pub trait MidiMessage {
710 fn pack(&self) -> &[u8];
711}
712
713macro_rules! impl_midi_message {
714 ($type:ty) => {
715 impl MidiMessage for $type {
716 fn pack(&self) -> &[u8] {
717 &self.bytes
718 }
719 }
720 };
721}
722
723pub(crate) use impl_midi_message;