Skip to content

Commit 2182847

Browse files
committed
Midi changes.
1 parent f0e671e commit 2182847

File tree

8 files changed

+90
-48
lines changed

8 files changed

+90
-48
lines changed

crates/processing_core/src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,6 @@ pub enum ProcessingError {
4242
ShaderCompilationError(String),
4343
#[error("Shader not found")]
4444
ShaderNotFound,
45+
#[error("MIDI port {0} not found")]
46+
MidiPortNotFound(usize),
4547
}

crates/processing_midi/src/lib.rs

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
use bevy::prelude::*;
22
use bevy_midi::prelude::*;
33

4-
use processing_core::error::Result;
4+
use processing_core::app_mut;
5+
use processing_core::error::{self, Result};
56

67
pub struct MidiPlugin;
78

9+
pub const NOTE_ON: u8 = 0b1001_0000;
10+
pub const NOTE_OFF: u8 = 0b1000_0000;
11+
812
impl Plugin for MidiPlugin {
913
fn build(&self, app: &mut App) {
1014
// TODO: Update `bevy_midi` to treat connections as entities
@@ -18,10 +22,13 @@ impl Plugin for MidiPlugin {
1822
}
1923

2024
pub fn connect(In(port): In<usize>, output: Res<MidiOutput>) -> Result<()> {
21-
if let Some((_, port)) = output.ports().get(port) {
22-
output.connect(port.clone());
25+
match output.ports().get(port) {
26+
Some((_, p)) => {
27+
output.connect(p.clone());
28+
Ok(())
29+
}
30+
None => Err(error::ProcessingError::MidiPortNotFound(port)),
2331
}
24-
Ok(())
2532
}
2633

2734
pub fn disconnect(output: Res<MidiOutput>) -> Result<()> {
@@ -34,12 +41,69 @@ pub fn refresh_ports(output: Res<MidiOutput>) -> Result<()> {
3441
Ok(())
3542
}
3643

44+
pub fn list_ports(output: Res<MidiOutput>) -> Result<Vec<String>> {
45+
Ok(output
46+
.ports()
47+
.iter()
48+
.enumerate()
49+
.map(|(i, (name, _))| format!("{}: {}", i, name))
50+
.collect())
51+
}
52+
3753
pub fn play_notes(In((note, duration)): In<(u8, u64)>, output: Res<MidiOutput>) -> Result<()> {
38-
output.send([0b1001_0000, note, 127].into()); // Note on, channel 1, max velocity
54+
output.send([NOTE_ON, note, 127].into()); // Note on, channel 1, max velocity
3955

4056
std::thread::sleep(std::time::Duration::from_millis(duration));
4157

42-
output.send([0b1000_0000, note, 127].into()); // Note on, channel 1, max velocity
58+
output.send([NOTE_OFF, note, 127].into()); // Note off, channel 1, max velocity
4359

4460
Ok(())
4561
}
62+
63+
#[cfg(not(target_arch = "wasm32"))]
64+
pub fn midi_refresh_ports() -> error::Result<()> {
65+
app_mut(|app| {
66+
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);
73+
Ok(())
74+
})
75+
}
76+
77+
#[cfg(not(target_arch = "wasm32"))]
78+
pub fn midi_list_ports() -> error::Result<Vec<String>> {
79+
app_mut(|app| {
80+
let world = app.world_mut();
81+
world.run_system_cached(list_ports).unwrap()
82+
})
83+
}
84+
85+
#[cfg(not(target_arch = "wasm32"))]
86+
pub fn midi_connect(port: usize) -> error::Result<()> {
87+
app_mut(|app| {
88+
let world = app.world_mut();
89+
world.run_system_cached_with(connect, port).unwrap()
90+
})
91+
}
92+
93+
#[cfg(not(target_arch = "wasm32"))]
94+
pub fn midi_disconnect() -> error::Result<()> {
95+
app_mut(|app| {
96+
let world = app.world_mut();
97+
world.run_system_cached(disconnect).unwrap()
98+
})
99+
}
100+
101+
#[cfg(not(target_arch = "wasm32"))]
102+
pub fn midi_play_notes(note: u8, duration: u64) -> error::Result<()> {
103+
app_mut(|app| {
104+
let world = app.world_mut();
105+
world
106+
.run_system_cached_with(play_notes, (note, duration))
107+
.unwrap()
108+
})
109+
}

crates/processing_pyo3/examples/midi.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
def setup():
55
size(800, 600)
66

7-
# Refresh midi port list, and connect to first one
7+
# Refresh midi port list, print available ports, and connect to first one
88
midi_refresh_ports()
9+
for port in midi_list_ports():
10+
print(port)
911
midi_connect(0)
1012

1113
def draw():

crates/processing_pyo3/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ fn processing(m: &Bound<'_, PyModule>) -> PyResult<()> {
9999
m.add_function(wrap_pyfunction!(midi_connect, m)?)?;
100100
m.add_function(wrap_pyfunction!(midi_disconnect, m)?)?;
101101
m.add_function(wrap_pyfunction!(midi_refresh_ports, m)?)?;
102+
m.add_function(wrap_pyfunction!(midi_list_ports, m)?)?;
102103
m.add_function(wrap_pyfunction!(midi_play_notes, m)?)?;
103104

104105
#[cfg(feature = "webcam")]
@@ -609,6 +610,10 @@ fn midi_refresh_ports() -> PyResult<()> {
609610
midi::refresh_ports()
610611
}
611612
#[pyfunction]
613+
fn midi_list_ports() -> PyResult<Vec<String>> {
614+
midi::list_ports()
615+
}
616+
#[pyfunction]
612617
fn midi_play_notes(note: u8, duration: u64) -> PyResult<()> {
613618
midi::play_notes(note, duration)
614619
}

crates/processing_pyo3/src/midi.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ pub fn disconnect() -> PyResult<()> {
1010
pub fn refresh_ports() -> PyResult<()> {
1111
midi_refresh_ports().map_err(|e| PyRuntimeError::new_err(format!("{e}")))
1212
}
13+
pub fn list_ports() -> PyResult<Vec<String>> {
14+
midi_list_ports().map_err(|e| PyRuntimeError::new_err(format!("{e}")))
15+
}
1316
pub fn play_notes(note: u8, duration: u64) -> PyResult<()> {
1417
midi_play_notes(note, duration).map_err(|e| PyRuntimeError::new_err(format!("{e}")))
1518
}

crates/processing_render/src/lib.rs

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,43 +1260,3 @@ pub fn gltf_light(gltf_entity: Entity, index: usize) -> error::Result<Entity> {
12601260
.unwrap()
12611261
})
12621262
}
1263-
1264-
#[cfg(not(target_arch = "wasm32"))]
1265-
pub fn midi_refresh_ports() -> error::Result<()> {
1266-
app_mut(|app| {
1267-
let world = app.world_mut();
1268-
world
1269-
.run_system_cached(processing_midi::refresh_ports)
1270-
.unwrap()
1271-
})
1272-
}
1273-
1274-
#[cfg(not(target_arch = "wasm32"))]
1275-
pub fn midi_connect(port: usize) -> error::Result<()> {
1276-
app_mut(|app| {
1277-
let world = app.world_mut();
1278-
world
1279-
.run_system_cached_with(processing_midi::connect, port)
1280-
.unwrap()
1281-
})
1282-
}
1283-
1284-
#[cfg(not(target_arch = "wasm32"))]
1285-
pub fn midi_disconnect() -> error::Result<()> {
1286-
app_mut(|app| {
1287-
let world = app.world_mut();
1288-
world
1289-
.run_system_cached(processing_midi::disconnect)
1290-
.unwrap()
1291-
})
1292-
}
1293-
1294-
#[cfg(not(target_arch = "wasm32"))]
1295-
pub fn midi_play_notes(note: u8, duration: u64) -> error::Result<()> {
1296-
app_mut(|app| {
1297-
let world = app.world_mut();
1298-
world
1299-
.run_system_cached_with(processing_midi::play_notes, (note, duration))
1300-
.unwrap()
1301-
})
1302-
}

examples/midi.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ fn sketch() -> error::Result<()> {
2929
let graphics = graphics_create(surface, width, height, TextureFormat::Rgba16Float)?;
3030

3131
midi_refresh_ports()?;
32-
midi_connect(0)?;
32+
for port in midi_list_ports()? {
33+
println!("{port}");
34+
}
35+
midi_connect(1)?;
3336

3437
let mut rng = rand::rng();
3538

src/prelude.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
pub use bevy::prelude::default;
22
pub use bevy::render::render_resource::TextureFormat;
33
pub use processing_core::{config::*, error};
4+
pub use processing_midi::{
5+
midi_connect, midi_disconnect, midi_list_ports, midi_play_notes, midi_refresh_ports,
6+
};
47
pub use processing_render::{
58
render::command::{DrawCommand, StrokeCapMode, StrokeJoinMode},
69
*,

0 commit comments

Comments
 (0)