HTMAA 2024 - Week 12

← Back to main schedule

User Interfaces: MIDI Streaming Web Interface

For this week, I created a Flask-hosted web interface to select MIDI files and stream them over WiFi to ESP32-C3 clients. The interface allows users to:

Here is the final result in action:

Flask Web Interface Code

The Flask app serves a simple web interface for selecting and streaming MIDI files. The server communicates over sockets to send MIDI messages to connected ESP clients:

import socket
import select
import time
import mido
import os
from flask import Flask, render_template_string, request, redirect, url_for

app = Flask(__name__)

MIDI_DIR = 'midi'
HOST = ''
PORT = 5001
selected_file = None
desired_bpm = 120
clients = []

@app.route("/", methods=["GET", "POST"])
def index():
    global selected_file, desired_bpm
    midi_files = [f for f in os.listdir(MIDI_DIR) if f.endswith('.mid')]

    if request.method == "POST":
        selected_file = os.path.join(MIDI_DIR, request.form.get("file"))
        desired_bpm = int(request.form.get("bpm", 120))
        start_socket_server(selected_file, desired_bpm)
        return redirect(url_for("index"))

    return render_template_string(HTML_TEMPLATE, midi_files=midi_files, selected_file=selected_file, bpm=desired_bpm)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080, debug=True)
            

ESP32-C3 Client Code

The ESP32-C3 connects to the server and listens for MIDI messages. It maps received note events to solenoids, toggling their state:

#include <WiFi.h>

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 isOn = (command == 0x90 && message[2] > 0);
            handleNoteEvent(note, isOn);
        }
    }
}

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: Final Project →