Skip to main content

coremidi_sys/
lib.rs

1#![cfg(any(target_os = "macos", target_os = "ios"))]
2
3#![allow(non_snake_case, non_upper_case_globals, non_camel_case_types)]
4
5extern crate core_foundation_sys;
6
7use core_foundation_sys::string::*;
8use core_foundation_sys::data::*;
9use core_foundation_sys::dictionary::*;
10use core_foundation_sys::propertylist::*;
11
12use std::{ptr, mem};
13
14include!("generated.rs");
15
16#[inline]
17#[must_use] 
18pub unsafe fn MIDIPacketNext(pkt: *const MIDIPacket) -> *const MIDIPacket {
19    // Get pointer to potentially unaligned data without triggering undefined behavior
20    // addr_of does not require creating an intermediate reference to unaligned data.
21    // See also the definition of `MIDIPacketNext` in the official SDK MIDIServices.h
22    let ptr = ptr::addr_of!((*pkt).data).cast::<u8>();
23    let ptr_length = ptr::addr_of!((*pkt).length).cast::<u16>();
24    if cfg!(any(target_arch = "arm", target_arch = "aarch64")) {
25        // MIDIPacket must be 4-byte aligned on ARM, so we need to calculate an aligned offset.
26        // We do not need `read_unaligned` for the length, because the length will never
27        // be unaligned, and `read_unaligned` would lead to less efficient machine code.
28        let offset = ptr_length.read() as isize;
29        ((ptr.offset(offset + 3) as usize) & !(3usize)) as *const MIDIPacket
30    } else {
31        // MIDIPacket is unaligned on non-ARM, so reading the length requires `read_unaligned`
32        // to not trigger Rust's UB check (although unaligned reads are harmless on Intel
33        // and `read_unaligned` will generate the same machine code as `read`).
34        let offset = ptr_length.read_unaligned() as isize;
35        ptr.offset(offset).cast::<MIDIPacket>()
36    }
37}
38
39#[inline]
40#[must_use] 
41pub unsafe fn MIDIEventPacketNext(pkt: *const MIDIEventPacket) -> *const MIDIEventPacket {
42    // Each EventPacket's size is a multiple of 4 bytes, so no special care
43    // needs to be taken when reading the data (except the timeStamp, which is not 8-byte aligned).
44    // See also the definition of `MIDIEventPacketNext` in the official SDK MIDIServices.h
45    let ptr = ptr::addr_of!((*pkt).words).cast::<u8>();
46    let offset = (((*pkt).wordCount as usize) * mem::size_of::<u32>()) as isize;
47    ptr.offset(offset).cast::<MIDIEventPacket>()
48}
49
50#[allow(dead_code)]
51mod static_test {
52    /// Statically assert the correct size of `MIDIPacket` and `MIDIPacketList`,
53    /// which require non-default alignment.
54    unsafe fn assert_sizes() {
55        use super::{MIDIPacket, MIDIPacketList};
56        use std::mem::{transmute, zeroed};
57
58        let p: MIDIPacket = zeroed();
59        transmute::<MIDIPacket, [u8; 268]>(p);
60
61        let p: MIDIPacketList = zeroed();
62        transmute::<MIDIPacketList, [u8; 272]>(p);
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn midi_packet_next() {
72        const BUFFER_SIZE: usize = 65536;
73        let buffer: &mut [u8] = &mut [0; BUFFER_SIZE];
74        let pkt_list_ptr = buffer.as_mut_ptr().cast::<MIDIPacketList>();
75
76        let packets = vec![
77            (1, vec![0x90, 0x40, 0x7f]), // tuple of (time, [midi bytes])
78            (2, vec![0x90, 0x41, 0x7f]),
79        ];
80
81        unsafe {
82            let mut pkt_ptr = MIDIPacketListInit(pkt_list_ptr);
83            for pkt in &packets {
84                pkt_ptr = MIDIPacketListAdd(
85                    pkt_list_ptr,
86                    BUFFER_SIZE as ByteCount,
87                    pkt_ptr,
88                    pkt.0,
89                    pkt.1.len() as ByteCount,
90                    pkt.1.as_ptr(),
91                );
92                assert!(!pkt_ptr.is_null());
93            }
94        }
95
96        unsafe {
97            let first_packet = &(*pkt_list_ptr).packet as *const MIDIPacket; // get pointer to first midi packet in the list
98            let len = (*first_packet).length as usize;
99            assert_eq!(
100                std::slice::from_raw_parts((*first_packet).data.as_ptr(), len),
101                &[0x90, 0x40, 0x7f]
102            );
103
104            let second_packet = MIDIPacketNext(first_packet);
105            let ptr_length = ptr::addr_of!((*second_packet).length).cast::<u16>();
106            let len = ptr_length.read_unaligned() as usize;
107            assert_eq!(
108                std::slice::from_raw_parts((*second_packet).data.as_ptr(), len),
109                &[0x90, 0x41, 0x7f]
110            );
111        }
112    }
113
114    #[test]
115    fn midi_event_packet_next() {
116        const BUFFER_SIZE: usize = 65536;
117        let buffer: &mut [u8] = &mut [0; BUFFER_SIZE];
118        let pkt_list_ptr = buffer.as_mut_ptr().cast::<MIDIEventList>();
119
120        let packets = vec![
121            (1, vec![10u32, 20]), // tuple of (time, [midi words])
122            (2, vec![30u32, 40, 50]),
123        ];
124
125        unsafe {
126            let mut pkt_ptr = MIDIEventListInit(pkt_list_ptr, kMIDIProtocol_2_0 as MIDIProtocolID);
127            for pkt in &packets {
128                pkt_ptr = MIDIEventListAdd(
129                    pkt_list_ptr,
130                    BUFFER_SIZE as ByteCount,
131                    pkt_ptr,
132                    pkt.0,
133                    pkt.1.len() as ByteCount,
134                    pkt.1.as_ptr(),
135                );
136                assert!(!pkt_ptr.is_null());
137            }
138        }
139
140        unsafe {
141            let first_packet = &(*pkt_list_ptr).packet as *const MIDIEventPacket; // get pointer to first midi packet in the list
142            let len = (*first_packet).wordCount as usize;
143            assert_eq!(
144                std::slice::from_raw_parts((*first_packet).words.as_ptr(), len),
145                &[10, 20]
146            );
147
148            let second_packet = MIDIEventPacketNext(first_packet);
149            let len = (*second_packet).wordCount as usize;
150            assert_eq!(
151                std::slice::from_raw_parts((*second_packet).words.as_ptr(), len),
152                &[30, 40, 50]
153            );
154        }
155    }
156}