Simulating a USB MIDI device with an ESP32 using Rust

Share
Simulating a USB MIDI device with an ESP32 using Rust

Imagine plugging an ESP32 into your computer’s USB port - and suddenly, your OS detects a brand-new MIDI device. You connect your phone to the WiFi network the ESP32 is hosting, open your browser, and - boom - a web keyboard appears. You play notes on it, and they’re instantly sent to your computer’s music software, with zero noticeable lag.

web.png

It sounds like magic. But it’s not. It’s just a lot of small, interconnected systems working together:

  • A USB stack to pretend the ESP32 is a MIDI device.
  • A WiFi access point and DHCP server so your phone can join the network.
  • An HTTP server to serve up a web app.
  • A WebSocket connection to send MIDI data with low latency.
  • And a DNS server to trigger a captive portal so you don’t have to type 192.168.1.1 by hand.

It’s a lot for a tiny microcontroller to juggle.

For years, I stuck with the Arduino IDE for quick projects. It was fine... until it wasn’t. Complex tasks meant wrestling with C, managing memory manually, and debugging obscure crashes that only happened under load. PlatformIO helped a bit, but I still found myself deal with unsafe code and raw pointers in places I didn’t want to think about.

Then I tried Rust.

I didn’t go in looking to change the world. I just wanted to write code that felt better. Rust’s ownership model meant I could finally stop worrying about memory bugs without sacrificing performance. Its async/await system let me write code that looked simple, even when it was doing ten things at once. And then I found Embassy.

Embassy is what made it click. It brings all those networking stacks - USB, WiFi, HTTP, WebSocket, DNS - not as brittle, platform-specific libraries, but as clean, composable crates. You write async functions like you’re writing regular code, and the runtime handles the scheduling. On an ESP32. With 512KB of RAM. It just works.

What I love most isn’t any one feature - it’s how everything fits together. Rust’s traits mean a USB device, a network interface, or an I2C sensor aren’t tied to specific hardware. They’re concepts. So if you switch from an ESP32 to a Raspberry Pi Pico, most of your code just keeps working. You swap a couple of crates, and you’re off.

It’s not about being the best programming language. It’s about writing code that’s easier to reason about, easier to extend, and less likely to break when you add one more thing (which you always do).

I’ve built a few little projects now - this MIDI controller was the first where I felt like I finally had the tools to build something complex without drowning in complexity. If you’ve ever wanted to make something like this but felt held back by the tools, I’d love for you to take a look at the code...

Click here to view the Source Code

GitHub - cjdell/esp32s3-midi: Simulate USB MIDI using an ESP32-S3. Send notes via GPIO or WiFi (WebSocket)
Simulate USB MIDI using an ESP32-S3. Send notes via GPIO or WiFi (WebSocket) - cjdell/esp32s3-midi