This week, I explored networking by implementing MIDI over WiFi. My setup included a Python-based server on my computer and an ESP32-C3 client. The server streams a MIDI file, note by note, to the ESP32-C3, which activates solenoids corresponding to MIDI notes.
Here's the demonstration:
The Python script streams MIDI messages to connected clients over a socket. Each message is serialized before transmission to ensure proper parsing on the client side. Here's the code:
import socket import select import time import mido def bpm_to_tempo(bpm: float) -> int: return int(60_000_000 / bpm) def adjust_midi_tempo(mid: mido.MidiFile, new_bpm: float): new_tempo = mido.bpm2tempo(new_bpm) for track in mid.tracks: for msg in track: if msg.type == 'set_tempo': msg.tempo = new_tempo def serialize_midi_message(msg: mido.Message) -> bytes: raw = msg.bin() length = len(raw) return length.to_bytes(1, 'big') + raw def main(): midi_file_path = 'midi/on_off_note_60.mid' new_bpm = float(input("Enter desired BPM (or blank for original): ") or 0) host = '' port = 5001 mid = mido.MidiFile(midi_file_path) if new_bpm > 0: adjust_midi_tempo(mid, new_bpm) server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_sock.bind((host, port)) server_sock.listen(5) print(f"Server listening on port {port}...") inputs = [server_sock] while len(inputs) > 1: readable, _, _ = select.select(inputs, [], []) for s in readable: if s is server_sock: conn, _ = s.accept() inputs.append(conn) else: inputs.remove(s) for msg in mid.play(): data = serialize_midi_message(msg) for client in inputs: client.sendall(data) for client in inputs: client.close()
The ESP32-C3 connects to the server, receives MIDI messages, and activates the corresponding solenoids. Here's the code:
#includeconst char* ssid = "MIT"; const char* password = "CENSORED"; const char* host = "CENSORED"; const int port = 5001; #define NUM_SOLENOIDS 5 int handledNotes[NUM_SOLENOIDS] = {60, 61, 62, 63, 64}; int solenoidPins[NUM_SOLENOIDS] = {2, 3, 4, 5, 6}; WiFiClient client; void setup() { WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) delay(500); client.connect(host, port); for (int i = 0; i < NUM_SOLENOIDS; i++) { pinMode(solenoidPins[i], OUTPUT); } } void loop() { if (client.available() > 0) { uint8_t lengthByte; client.read(&lengthByte, 1); uint8_t message[256]; client.read(message, lengthByte); uint8_t status = message[0]; uint8_t command = status & 0xF0; if ((command == 0x90 || command == 0x80) && lengthByte >= 3) { uint8_t note = message[1]; bool isNoteOn = (command == 0x90 && message[2] > 0); handleNoteEvent(note, isNoteOn); } } } void handleNoteEvent(uint8_t note, bool isOn) { for (int i = 0; i < NUM_SOLENOIDS; i++) { if (handledNotes[i] == note) { digitalWrite(solenoidPins[i], isOn ? HIGH : LOW); } } }