11use bevy:: prelude:: * ;
2- use bevy_midi :: prelude :: * ;
2+ use nannou_midi :: { MidiOutput , MidiOutputStream , MidiPort , MidiPortDirection } ;
33
44use processing_core:: app_mut;
55use processing_core:: error:: { self , Result } ;
66
7+ pub use nannou_midi:: { MidiData , MidiMessage } ;
8+
79pub struct MidiPlugin ;
810
911pub const NOTE_ON : u8 = 0b1001_0000 ;
1012pub const NOTE_OFF : u8 = 0b1000_0000 ;
1113
14+ #[ derive( Resource , Default ) ]
15+ struct ActiveOutput ( Option < Entity > ) ;
16+
1217impl Plugin for MidiPlugin {
1318 fn build ( & self , app : & mut App ) {
14- // TODO: Update `bevy_midi` to treat connections as entities
15- // in order to support hot-plugging
16- app. insert_resource ( MidiOutputSettings {
17- port_name : "libprocessing output" ,
18- } ) ;
19-
20- app. add_plugins ( MidiOutputPlugin ) ;
19+ app. init_resource :: < ActiveOutput > ( ) ;
20+ app. add_plugins ( nannou_midi:: MidiPlugin ) ;
2121 }
2222}
2323
24- pub fn connect ( In ( port) : In < usize > , output : Res < MidiOutput > ) -> Result < ( ) > {
25- match output. ports ( ) . get ( port) {
26- Some ( ( _, p) ) => {
27- output. connect ( p. clone ( ) ) ;
28- Ok ( ( ) )
29- }
30- None => Err ( error:: ProcessingError :: MidiPortNotFound ( port) ) ,
31- }
32- }
33-
34- pub fn disconnect ( output : Res < MidiOutput > ) -> Result < ( ) > {
35- output. disconnect ( ) ;
36- Ok ( ( ) )
24+ fn sorted_output_ports ( world : & mut World ) -> Vec < ( Entity , String ) > {
25+ let mut q = world. query :: < ( Entity , & Name , & MidiPort ) > ( ) ;
26+ let mut ports: Vec < ( Entity , String ) > = q
27+ . iter ( world)
28+ . filter ( |( _, _, p) | p. direction == MidiPortDirection :: Output )
29+ . map ( |( e, n, _) | ( e, n. as_str ( ) . to_string ( ) ) )
30+ . collect ( ) ;
31+ ports. sort_by ( |a, b| a. 1 . cmp ( & b. 1 ) ) ;
32+ ports
3733}
3834
39- pub fn refresh_ports ( output : Res < MidiOutput > ) -> Result < ( ) > {
40- output. refresh_ports ( ) ;
41- Ok ( ( ) )
42- }
43-
44- pub fn list_ports ( output : Res < MidiOutput > ) -> Result < Vec < String > > {
45- Ok ( output
46- . ports ( )
47- . iter ( )
35+ pub fn list_ports ( world : & mut World ) -> Result < Vec < String > > {
36+ Ok ( sorted_output_ports ( world)
37+ . into_iter ( )
4838 . enumerate ( )
49- . map ( |( i, ( name , _ ) ) | format ! ( "{}: {}" , i , name ) )
39+ . map ( |( i, ( _ , name ) ) | format ! ( "{i }: {name}" ) )
5040 . collect ( ) )
5141}
5242
53- pub fn play_notes ( In ( ( note, duration) ) : In < ( u8 , u64 ) > , output : Res < MidiOutput > ) -> Result < ( ) > {
54- output. send ( [ NOTE_ON , note, 127 ] . into ( ) ) ; // Note on, channel 1, max velocity
43+ pub fn connect ( In ( port) : In < usize > , world : & mut World ) -> Result < ( ) > {
44+ let port_entity = sorted_output_ports ( world)
45+ . get ( port)
46+ . map ( |( e, _) | * e)
47+ . ok_or ( error:: ProcessingError :: MidiPortNotFound ( port) ) ?;
48+
49+ let previous = world. resource :: < ActiveOutput > ( ) . 0 ;
50+ if let Some ( e) = previous
51+ && let Ok ( entity) = world. get_entity_mut ( e)
52+ {
53+ entity. despawn ( ) ;
54+ }
5555
56- std:: thread:: sleep ( std:: time:: Duration :: from_millis ( duration) ) ;
56+ let connection = world
57+ . spawn ( (
58+ Name :: new ( "libprocessing output" ) ,
59+ MidiOutput {
60+ port : Some ( port_entity) ,
61+ } ,
62+ ) )
63+ . id ( ) ;
64+ world. resource_mut :: < ActiveOutput > ( ) . 0 = Some ( connection) ;
65+ Ok ( ( ) )
66+ }
5767
58- output. send ( [ NOTE_OFF , note, 127 ] . into ( ) ) ; // Note off, channel 1, max velocity
68+ pub fn disconnect ( world : & mut World ) -> Result < ( ) > {
69+ let entity = world. resource_mut :: < ActiveOutput > ( ) . 0 . take ( ) ;
70+ if let Some ( e) = entity
71+ && let Ok ( entity_mut) = world. get_entity_mut ( e)
72+ {
73+ entity_mut. despawn ( ) ;
74+ }
75+ Ok ( ( ) )
76+ }
5977
78+ pub fn send_message ( In ( msg) : In < MidiMessage > , world : & mut World ) -> Result < ( ) > {
79+ let entity = world
80+ . resource :: < ActiveOutput > ( )
81+ . 0
82+ . ok_or ( error:: ProcessingError :: MidiPortNotFound ( usize:: MAX ) ) ?;
83+ if let Some ( mut stream) = world. get_mut :: < MidiOutputStream > ( entity) {
84+ stream. send ( msg) ;
85+ }
6086 Ok ( ( ) )
6187}
6288
6389#[ cfg( not( target_arch = "wasm32" ) ) ]
6490pub fn midi_refresh_ports ( ) -> error:: Result < ( ) > {
6591 app_mut ( |app| {
6692 let world = app. world_mut ( ) ;
67- world. run_system_cached ( refresh_ports) . unwrap ( )
68- } ) ?;
69- // run the `PreUpdate` schedule to let `bevy_midi` process it's callbacks and update the ports list
70- // TODO: race condition is still present here in theory
71- app_mut ( |app| {
72- app. world_mut ( ) . run_schedule ( PreUpdate ) ;
93+ world
94+ . run_system_cached ( nannou_midi:: native:: enumerate_midi_ports)
95+ . unwrap ( ) ;
7396 Ok ( ( ) )
7497 } )
7598}
@@ -86,7 +109,12 @@ pub fn midi_list_ports() -> error::Result<Vec<String>> {
86109pub fn midi_connect ( port : usize ) -> error:: Result < ( ) > {
87110 app_mut ( |app| {
88111 let world = app. world_mut ( ) ;
89- world. run_system_cached_with ( connect, port) . unwrap ( )
112+ world. run_system_cached_with ( connect, port) . unwrap ( ) ?;
113+ // Materialize the MidiOutputStream component before the caller sends.
114+ world
115+ . run_system_cached ( nannou_midi:: native:: open_midi_outputs)
116+ . unwrap ( ) ;
117+ Ok ( ( ) )
90118 } )
91119}
92120
@@ -98,12 +126,57 @@ pub fn midi_disconnect() -> error::Result<()> {
98126 } )
99127}
100128
129+ #[ cfg( not( target_arch = "wasm32" ) ) ]
130+ pub fn midi_note_on ( note : u8 , velocity : u8 ) -> error:: Result < ( ) > {
131+ app_mut ( |app| {
132+ let world = app. world_mut ( ) ;
133+ world
134+ . run_system_cached_with ( send_message, MidiMessage :: from ( [ NOTE_ON , note, velocity] ) )
135+ . unwrap ( ) ?;
136+ world
137+ . run_system_cached ( nannou_midi:: native:: send_midi_messages)
138+ . unwrap ( ) ;
139+ Ok ( ( ) )
140+ } )
141+ }
142+
143+ #[ cfg( not( target_arch = "wasm32" ) ) ]
144+ pub fn midi_note_off ( note : u8 ) -> error:: Result < ( ) > {
145+ app_mut ( |app| {
146+ let world = app. world_mut ( ) ;
147+ world
148+ . run_system_cached_with ( send_message, MidiMessage :: from ( [ NOTE_OFF , note, 0 ] ) )
149+ . unwrap ( ) ?;
150+ world
151+ . run_system_cached ( nannou_midi:: native:: send_midi_messages)
152+ . unwrap ( ) ;
153+ Ok ( ( ) )
154+ } )
155+ }
156+
101157#[ cfg( not( target_arch = "wasm32" ) ) ]
102158pub fn midi_play_notes ( note : u8 , duration : u64 ) -> error:: Result < ( ) > {
103159 app_mut ( |app| {
104160 let world = app. world_mut ( ) ;
105161 world
106- . run_system_cached_with ( play_notes, ( note, duration) )
107- . unwrap ( )
162+ . run_system_cached_with ( send_message, MidiMessage :: from ( [ NOTE_ON , note, 127 ] ) )
163+ . unwrap ( ) ?;
164+ world
165+ . run_system_cached ( nannou_midi:: native:: send_midi_messages)
166+ . unwrap ( ) ;
167+ Ok ( ( ) )
168+ } ) ?;
169+
170+ std:: thread:: sleep ( std:: time:: Duration :: from_millis ( duration) ) ;
171+
172+ app_mut ( |app| {
173+ let world = app. world_mut ( ) ;
174+ world
175+ . run_system_cached_with ( send_message, MidiMessage :: from ( [ NOTE_OFF , note, 127 ] ) )
176+ . unwrap ( ) ?;
177+ world
178+ . run_system_cached ( nannou_midi:: native:: send_midi_messages)
179+ . unwrap ( ) ;
180+ Ok ( ( ) )
108181 } )
109182}
0 commit comments