koto_midi/
lib.rs

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
11// TODO: Solve unnecessary repetition of list collectors for different types ot cases if there is.
12pub 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                // Truncate.
22                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                // Truncate.
43                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                // Truncate.
63                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                                // dbg!($(*$field),*);
141                                let message = <$enum_key>::new($(*$field),*);
142                                // dbg!(&message);
143                                message_koto.insert("type", name_literal.clone());
144                                message_koto.insert("category", $category_literal);
145                                $(
146                                //   dbg!($field);
147                                  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    // TODO: This is a very basic sysex implementation. It might be extended later. Find out if it is necessary.
387
388    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    // TODO: Find out what are possible message types and values for time_code_quarter_frame
440
441    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                        // Returns an empty value if the message is malformed.
691                        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;