HTMAA 2024 - Week 11

← Back to main schedule

Networking: MIDI over WiFi

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:

Python Server Code

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()
            

ESP32-C3 Client Code

The ESP32-C3 connects to the server, receives MIDI messages, and activates the corresponding solenoids. Here's the code:

#include 

const 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);
        }
    }
}
            

Next Week: Wildcard Week →