Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 94 additions & 69 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ web-sys = { version = "0.3", features = ["Window"] }

[dev-dependencies]
glfw = "0.60.0"
rand = "0.10.0"

[target.'cfg(target_os = "linux")'.dev-dependencies]
glfw = { version = "0.60.0", features = ["wayland"] }
Expand Down
2 changes: 1 addition & 1 deletion crates/processing_midi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ edition = "2024"

[dependencies]
bevy = { workspace = true }
processing_core = { workspace = true }
bevy_midi = { git = "https://github.com/BlackPhlox/bevy_midi", branch = "latest" }


[lints]
workspace = true
40 changes: 31 additions & 9 deletions crates/processing_midi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,45 @@
use bevy::prelude::*;
use bevy_midi::prelude::*;

use processing_core::error::Result;

pub struct MidiPlugin;

impl Plugin for MidiPlugin {
fn build(&self, app: &mut App) {
// TODO: Update `bevy_midi` to treat connections as entities
// in order to support hot-plugging
app.insert_resource(MidiOutputSettings {
port_name: "output",
})
.add_plugins(MidiOutputPlugin);
port_name: "libprocessing output",
});

app.add_plugins(MidiOutputPlugin);
}
}

pub fn connect(In(port): In<usize>, output: Res<MidiOutput>) -> Result<()> {
if let Some((_, port)) = output.ports().get(port) {
output.connect(port.clone());
}
Ok(())
}

pub fn connect(_port: usize) {
// we need to work with the ECS
// do we pass a MidiCommand to Bevy?
pub fn disconnect(output: Res<MidiOutput>) -> Result<()> {
output.disconnect();
Ok(())
}

pub fn disconnect() {}
pub fn refresh_ports() {}
pub fn refresh_ports(output: Res<MidiOutput>) -> Result<()> {
output.refresh_ports();
Ok(())
}

pub fn play_notes(In((note, duration)): In<(u8, u64)>, output: Res<MidiOutput>) -> Result<()> {
output.send([0b1001_0000, note, 127].into()); // Note on, channel 1, max velocity

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

pub fn play_notes() {}
output.send([0b1000_0000, note, 127].into()); // Note on, channel 1, max velocity

Ok(())
}
26 changes: 26 additions & 0 deletions crates/processing_pyo3/examples/midi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from processing import *
import random

def setup():
size(800, 600)

# Refresh midi port list, and connect to first one
midi_refresh_ports()
midi_connect(0)

def draw():
background(220)

fill(255, 0, 100)
stroke(1)
stroke_weight(2)
rect(100, 100, 200, 150)

# pick a random note value, and duration value for that note
# then send the midi command
note = random.randint(57,68)
note_duration = random.randint(25, 250)
midi_play_notes(note, note_duration)

# TODO: this should happen implicitly on module load somehow
run()
23 changes: 23 additions & 0 deletions crates/processing_pyo3/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ mod glfw;
mod gltf;
mod graphics;
pub(crate) mod material;
mod midi;
pub(crate) mod shader;
#[cfg(feature = "webcam")]
mod webcam;

use graphics::{Geometry, Graphics, Image, Light, Topology, get_graphics, get_graphics_mut};
use material::Material;

use pyo3::{
exceptions::PyRuntimeError,
prelude::*,
Expand Down Expand Up @@ -94,6 +96,10 @@ fn processing(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(metallic, m)?)?;
m.add_function(wrap_pyfunction!(emissive, m)?)?;
m.add_function(wrap_pyfunction!(unlit, m)?)?;
m.add_function(wrap_pyfunction!(midi_connect, m)?)?;
m.add_function(wrap_pyfunction!(midi_disconnect, m)?)?;
m.add_function(wrap_pyfunction!(midi_refresh_ports, m)?)?;
m.add_function(wrap_pyfunction!(midi_play_notes, m)?)?;

#[cfg(feature = "webcam")]
{
Expand Down Expand Up @@ -589,3 +595,20 @@ fn create_webcam(
) -> PyResult<webcam::Webcam> {
webcam::Webcam::new(width, height, framerate)
}

#[pyfunction]
fn midi_connect(port: usize) -> PyResult<()> {
midi::connect(port)
}
#[pyfunction]
fn midi_disconnect() -> PyResult<()> {
midi::disconnect()
}
#[pyfunction]
fn midi_refresh_ports() -> PyResult<()> {
midi::refresh_ports()
}
#[pyfunction]
fn midi_play_notes(note: u8, duration: u64) -> PyResult<()> {
midi::play_notes(note, duration)
}
15 changes: 15 additions & 0 deletions crates/processing_pyo3/src/midi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use processing::prelude::*;
use pyo3::{exceptions::PyRuntimeError, prelude::*};

pub fn connect(port: usize) -> PyResult<()> {
midi_connect(port).map_err(|e| PyRuntimeError::new_err(format!("{e}")))
}
pub fn disconnect() -> PyResult<()> {
midi_disconnect().map_err(|e| PyRuntimeError::new_err(format!("{e}")))
}
pub fn refresh_ports() -> PyResult<()> {
midi_refresh_ports().map_err(|e| PyRuntimeError::new_err(format!("{e}")))
}
pub fn play_notes(note: u8, duration: u64) -> PyResult<()> {
midi_play_notes(note, duration).map_err(|e| PyRuntimeError::new_err(format!("{e}")))
}
1 change: 1 addition & 0 deletions crates/processing_render/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ raw-window-handle = "0.6"
half = "2.7"
crossbeam-channel = "0.5"
processing_core = { workspace = true }
processing_midi = { workspace = true }

[build-dependencies]
wesl = { workspace = true, features = ["package"] }
Expand Down
52 changes: 45 additions & 7 deletions crates/processing_render/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,11 @@ impl Plugin for ProcessingRenderPlugin {
let has_sketch_file = config
.get(ConfigKey::SketchFileName)
.is_some_and(|f| !f.is_empty());
if has_sketch_file {
if let Some(sketch_path) = config.get(ConfigKey::SketchRootPath) {
app.register_asset_source(
"sketch_directory",
AssetSourceBuilder::platform_default(sketch_path, None),
);
}
if has_sketch_file && let Some(sketch_path) = config.get(ConfigKey::SketchRootPath) {
app.register_asset_source(
"sketch_directory",
AssetSourceBuilder::platform_default(sketch_path, None),
);
}

if has_sketch_file {
Expand Down Expand Up @@ -1262,3 +1260,43 @@ pub fn gltf_light(gltf_entity: Entity, index: usize) -> error::Result<Entity> {
.unwrap()
})
}

#[cfg(not(target_arch = "wasm32"))]
pub fn midi_refresh_ports() -> error::Result<()> {
app_mut(|app| {
let world = app.world_mut();
world
.run_system_cached(processing_midi::refresh_ports)
.unwrap()
})
}

#[cfg(not(target_arch = "wasm32"))]
pub fn midi_connect(port: usize) -> error::Result<()> {
app_mut(|app| {
let world = app.world_mut();
world
.run_system_cached_with(processing_midi::connect, port)
.unwrap()
})
}

#[cfg(not(target_arch = "wasm32"))]
pub fn midi_disconnect() -> error::Result<()> {
app_mut(|app| {
let world = app.world_mut();
world
.run_system_cached(processing_midi::disconnect)
.unwrap()
})
}

#[cfg(not(target_arch = "wasm32"))]
pub fn midi_play_notes(note: u8, duration: u64) -> error::Result<()> {
app_mut(|app| {
let world = app.world_mut();
world
.run_system_cached_with(processing_midi::play_notes, (note, duration))
.unwrap()
})
}
12 changes: 11 additions & 1 deletion examples/midi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use glfw::GlfwContext;
use processing::prelude::*;
use processing_render::render::command::DrawCommand;

use rand::prelude::*;

fn main() {
match sketch() {
Ok(_) => {
Expand All @@ -26,7 +28,10 @@ fn sketch() -> error::Result<()> {
let surface = glfw_ctx.create_surface(width, height)?;
let graphics = graphics_create(surface, width, height, TextureFormat::Rgba16Float)?;

processing_midi::connect(0);
midi_refresh_ports()?;
midi_connect(0)?;

let mut rng = rand::rng();

while glfw_ctx.poll_events() {
graphics_begin_draw(graphics)?;
Expand All @@ -43,6 +48,11 @@ fn sketch() -> error::Result<()> {
)?;

graphics_end_draw(graphics)?;

let note = rng.random_range(57..68);
let note_duration = rng.random_range(25..250);
midi_play_notes(note, note_duration)?;
}

Ok(())
}
Loading