User:Ssstephen/take-the-entrain: Difference between revisions

From XPUB & Lens-Based wiki
 
(12 intermediate revisions by the same user not shown)
Line 1: Line 1:
this room for sound residency will be an exploration of tying digital connections and using them as an instrument. connexion used to be spelled with an x which shows the origin of the word: a binding or joining together. opening a network connexion creates a string of data which can and will be plucked. a work in the form of a net: strings interwoven and interconnetted. starting from short strings inside a single computer, over the two weeks this network will expand to cover the entire universe.
this room for sound residency will be an exploration of tying digital connections and using them as an instrument. connexion used to be spelled with an x which shows the origin of the word: a binding or joining together. opening a network connexion creates a string of data which can and will be plucked. a work in the form of a net: strings interwoven and interconnetted. starting from short strings inside a single computer, over the two weeks this network will expand to cover the entire universe.


This is a two week residency in the Willem de Kooning Academy's Room for Sound, from 16th to 27th January 2023. I will be experimenting with networked music, in particular I plan to use some ESP32 boards and a Raspberry Pi to send and receive RTP-MIDI over LAN and hopefully WAN, and to translate this MIDI data to and from audio (via microphones, sensors, buzzers, speakers, etc). I would like to get other people involved in this process as well as getting the machines to interact: the machines could be not just musicians but also instruments.  
This is a two week residency in the Willem de Kooning Academy's Room for Sound, from 16th to 27th January 2023. I will be experimenting with networked music, in particular I plan to use some ESP32 boards and a Raspberry Pi to send and receive rtpMIDI over LAN and hopefully WAN, and to translate this MIDI data to and from audio (via microphones, sensors, buzzers, speakers, etc). I would like to get other people involved in this process as well as getting the machines to interact: the machines could be not just musicians but also instruments.  


==Pseudocode==
==Pseudocode==
Line 330: Line 330:


Now they need to have a little chat with eachother.
Now they need to have a little chat with eachother.
==Day 5==
[[File:Room-for-sound-week-1.jpg|frameless|right|alt=room-for-sound-week-1|room-for-sound-week-1]]
===The day I get the ESP32 to send more interesting data===
[https://www.youtube.com/watch?v=0L7WAMFWSgY&list=PL4_gPbvyebyH2xfPXePHtx8gK5zPBrVkg&index=1 This playlist] gives me some information on how to MIDI on Arduino. But actually that is for MIDI over DIN, so maybe for a different day. [https://github.com/FortySevenEffects/arduino_midi_library This library] does lots of MIDI stuff including MIDI over serial and [https://github.com/lathoub/Arduino-BLE-MIDI BLE], and can later be expanded with [https://github.com/lathoub/Arduino-AppleMIDI-Library this] to do rtpMIDI.
Also tried [https://github.com/arduino-libraries/MIDIUSB MIDIUSB] where I learnt that an ESP32 does not in fact have USB communication, rather a serial port going down a USB wire. Interessant.
First I'm getting the ESP32 to send MIDI over BLE, using [https://gist.github.com/RyoKosaka/2a338a537d838911e23578c4595d9673 this script] and [https://www.korg.com/us/support/download/driver/1/305/2886/ this BLE MIDI driver]. The code below sends a 150bpm drum pattern over MIDI, while playing a melody itself on a buzzer. I really like how the synchronisation is all messed up, this is what I was hoping for. Next week the plan is to play with this effect some more and get it happeing on different networks. I am working with microsecond delays for the moment, but this is probably not the most accutate way to count time. Or maybe it is. The processor is 160Mhz so I'm not sure if I need to account for that, more likely I think it is to do with bluetooth latency (does bluetooth MIDI have strict timing? I doubt it).
[https://2223.mywdka.nl/roomforsound/sound/sync-problems/ It sounds like this.]
<pre>
#include <Arduino.h>
#include <BLEMIDI_Transport.h>
#include <hardware/BLEMIDI_ESP32.h>
BLEMIDI_CREATE_INSTANCE("Bammm",MIDI);
 
// Define some pitches of beeps, a basic time unit, a status
int melody[16] = {400, 360, 480, 400, 960, 200, 400, 360, 480, 400, 960, 200, 400, 360, 480, 400};
int melodyPos = 0;
int ledstatus=0;
int tempo = 100000; //a 16th note in 150bpm
// functions for making sounds
void ledSwitch(){
  //this part switches the LED status
  if(ledstatus==0){
    digitalWrite(D8,HIGH);
    ledstatus=1;
  }else{
    digitalWrite(D8,LOW);
    ledstatus=0;
  }
}
void clap(int tone1) {
  ledSwitch();
  tone(A0, tone1, 100);
  delayMicroseconds(tempo-10000);
  ledSwitch();
}
void setup()
{
  // initialize LED digital pin as an output, and buzzer analog pin.
  pinMode(D8, OUTPUT);
  pinMode(A0, OUTPUT);
  MIDI.begin(10);
}
void loop()
{
    for (int i =0; i<16; i++){
      if((i%4==0)||(i==14)){
        MIDI.sendNoteOn(36, 100, 10);
        MIDI.sendNoteOff(36, 0, 10);
      }
      MIDI.sendNoteOn(44, 100, 10);
      MIDI.sendNoteOff(44, 0, 10);
      if(i%3==0){
        clap(melody[melodyPos%16]);
        melodyPos++;
      }else{
        delayMicroseconds(tempo);
      }
    }
}
</pre>
Next week: rtpMIDI, receiving MIDI data to make sounds on the ESP, buttons, two ESPs communicating (via some sort of MIDI).
==Day 6==
===The day that rtpMIDI finally happens===
Today is the day. [https://github.com/lathoub/Arduino-AppleMIDI-Library This library] let me send MIDI on the internet. I also used [https://www.tobias-erichsen.de/software/rtpmidi.html this program] to do rtpMIDI on windows. So I am sending rtpMIDI over a wifi port and tomorrow I should finally be able to build the program from the pseudocode at the start wooo.
Listen to it [https://2223.mywdka.nl/roomforsound/sound/music-on-the-internet/ here].
<pre>
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <WiFiUdp.h>
#define SerialMon Serial
#include <AppleMIDI.h>
// #-------------------------------eduroam stuff------------------
#include "esp_wpa2.h" //wpa2 library for connections to Enterprise networks
//Identity for user with password related to his realm (organization)
//Available option of anonymous identity for federation of RADIUS servers or 1st Domain RADIUS servers
#define EAP_ANONYMOUS_IDENTITY "**********" //anonymous@example.com, or you can use also nickname@example.com
#define EAP_IDENTITY "***********" //nickname@example.com, at some organizations should work nickname only without realm, but it is not recommended
#define EAP_PASSWORD "********" //password for eduroam account
//SSID NAME
const char* ssid = "eduroam"; // eduroam SSID
int ledstatus=0;
void ledSwitch(){
  //this part switches the LED status
  if(ledstatus==0){
    digitalWrite(D8,HIGH);
    ledstatus=1;
  }else{
    digitalWrite(D8,LOW);
    ledstatus=0;
  }
}
void beepsweep(int tone1,int length, int direction) {
  ledSwitch();
  //This part pulses the buzzer.
  for (int tonecounter=1;tonecounter<=length;tonecounter=tonecounter+1){
    analogWrite(A0,250);
    delayMicroseconds(tone1*(abs((1-direction)*length-tonecounter+1)/10));
    analogWrite(A0,0);
    delayMicroseconds(tone1*(tonecounter/10));
    delayMicroseconds(tone1*(abs(direction*length-tonecounter+1)/10));
  }
  ledSwitch();
}
unsigned long t0 = millis();
int8_t isConnected = 0;
APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE();
// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
void setup()
{
  AM_DBG_SETUP(115200);
  delay(3000);
  AM_DBG(F("Booting"));
  pinMode(D8, OUTPUT);
  pinMode(A0, OUTPUT);
  // Serial.begin(115200);
  Serial.print(F("Connecting to network: "));
  Serial.println(ssid);
  WiFi.disconnect(true);  //disconnect form wifi to set new wifi connection
  WiFi.mode(WIFI_STA); //init wifi mode
  WiFi.begin(ssid, WPA2_AUTH_PEAP, EAP_ANONYMOUS_IDENTITY, EAP_IDENTITY, EAP_PASSWORD); //without CERTIFICATE
  // while (WiFi.status() != WL_CONNECTED) {
  //  delay(500);
  //  Serial.print(F("."));
  // }
  // Serial.println("");
  // Serial.println(F("WiFi is connected!"));
  // Serial.println(F("IP address set: "));
  // Serial.println(WiFi.localIP()); //print LAN IP
  // ledSwitch();
  // WiFi.begin(ssid, pass);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    AM_DBG(F("Establishing connection to WiFi.."));
  }
  AM_DBG(F("Connected to network"));
  ledSwitch();
  AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled"));
  AM_DBG(F("Add device named Arduino with Host"), WiFi.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")");
  AM_DBG(F("Select and then press the Connect button"));
  AM_DBG(F("Then open a MIDI listener and monitor incoming notes"));
  AM_DBG(F("Listen to incoming MIDI commands"));
  MIDI.begin();
  AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) {
    isConnected++;
    AM_DBG(F("Connected to session"), ssrc, name);
  });
  AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) {
    isConnected--;
    AM_DBG(F("Disconnected"), ssrc);
  });
 
  MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) {
    AM_DBG(F("NoteOn"), note);
  });
  MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) {
    AM_DBG(F("NoteOff"), note);
  });
  AM_DBG(F("Sending NoteOn/Off of note 45, every second"));
}
// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
void loop()
{
  // Listen to incoming notes
  MIDI.read();
  yield();
  if (WiFi.status() == WL_CONNECTED) {
    delay(4000);
    Serial.println("");
    Serial.println(F("WiFi is connected!"));
    Serial.println(F("IP address set: "));
    Serial.println(WiFi.localIP()); //print LAN IP
    AM_DBG(F("Add device named Arduino with Host"), WiFi.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")");
    ledSwitch();
  }
 
  // send a note every second
  // (dont cáll delay(1000) as it will stall the pipeline)
  if ((isConnected > 0) && (millis() - t0) > 1000)
  {
    t0 = millis();
    byte note = 45;
    byte velocity = 55;
    byte channel = 1;
    Serial.println(F("MIDI Sending"));
    MIDI.sendNoteOn(note, velocity, channel);
    MIDI.sendNoteOff(note, velocity, channel);
    beepsweep(600,120,1);
  }
}
</pre>
===And I also received MIDI on the ESPs===
That was pretty straightforward.
==Day 7==
===Latent Rhythms===
Today I sent rtpMIDI over the internet from one ESP32 to another, via some routing on my computer. The code below is one of a pair of programs that plays a beep after it receives a MIDI note, and sends a MIDI note back. If it doesnt hear a note at all for 2000ms, it beeps anyway. The latency was way more irregular than I expected making some really interesting rhythms. You can hear the network, and the network itself is making the music interesting not the individual performers.
[https://2223.mywdka.nl/roomforsound/sound/latent-rhythms/ It sounds like this]
===Other ways to use this===
What happens if there are more than two devices
What else can the MIDI control
What happens if replies send in the next MIDI channel up, but resets always send in channel one.
Can the 2000ms reset be adapted: trusting the other performers more and more if the latency is low.
Can you send CV gate from MIDI in using these ESP32s
What happens if a modular synth gets involved
Can it work on WAN as well as LAN
<pre>
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <WiFiUdp.h>
#define SerialMon Serial
#include <AppleMIDI.h>
// #-------------------------------eduroam stuff------------------
#include "esp_wpa2.h" //wpa2 library for connections to Enterprise networks
//Identity for user with password related to his realm (organization)
#define EAP_ANONYMOUS_IDENTITY "????????????????" //anonymous@example.com, or you can use also nickname@example.com
#define EAP_IDENTITY "!!!!!!!!!!!!!!" //nickname@example.com, at some organizations should work nickname only without realm, but it is not recommended
#define EAP_PASSWORD "££££££££££££" //password for eduroam account
const char* ssid = "$$$$$$$$$$$"; // eduroam SSID
// Other variables
int ledstatus=0;
int melodyPos=0;
int melody[16] = {63, 66, 65, 68, 65, 62, 61, 63, 65, 63, 57, 61, 63, 66, 65, 62};
float a = 660;
unsigned long t0 = millis();
int8_t isConnected = 0;
APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE();
// Functions
// #-------------------------------------
void ledSwitch(){
  //this part switches the LED status
  if(ledstatus==0){
    digitalWrite(D8,HIGH);
    ledstatus=1;
  }else{
    digitalWrite(D8,LOW);
    ledstatus=0;
  }
}
void beepsweep(int tone1,int length, int direction) {
  long delay = (500000/tone1);
  ledSwitch();
  AM_DBG(F("Delay in microseconds is "), delay);
  //This part pulses the buzzer.
  for (int tonecounter=1;tonecounter<=length;tonecounter=tonecounter+1){
    analogWrite(A0,250);
    delayMicroseconds(delay);
    analogWrite(A0,0);
    delayMicroseconds(delay);
  }
  ledSwitch();
}
// Running stuff
// #-------------------------------------------------------------------------
void setup()
{
  AM_DBG_SETUP(115200);
  delay(3000);
  AM_DBG(F("Booting"));
  pinMode(D8, OUTPUT);
  pinMode(A0, OUTPUT);
  // Serial.begin(115200);
  Serial.print(F("Connecting to network: "));
  Serial.println(ssid);
  WiFi.disconnect(true);  //disconnect form wifi to set new wifi connection
  WiFi.mode(WIFI_STA); //init wifi mode
  WiFi.begin(ssid, WPA2_AUTH_PEAP, EAP_ANONYMOUS_IDENTITY, EAP_IDENTITY, EAP_PASSWORD); //without CERTIFICATE
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    AM_DBG(F("Establishing connection to WiFi.."));
  }
  AM_DBG(F("Connected to network"));
  ledSwitch();
  AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled"));
  AM_DBG(F("Add device named Arduino with Host"), WiFi.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")");
  AM_DBG(F("Select and then press the Connect button"));
  AM_DBG(F("Then open a MIDI listener and monitor incoming notes"));
  AM_DBG(F("Listen to incoming MIDI commands"));
  MIDI.begin();
  AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) {
    isConnected++;
    AM_DBG(F("Connected to session"), ssrc, name);
  });
  AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) {
    isConnected--;
    AM_DBG(F("Disconnected"), ssrc);
  });
 
  MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) {
    if (channel == 3){
      t0 = millis();
     
      byte note = melody[melodyPos%16];
      byte velocity = rand() % 63 + 63;
      byte channel = 1;
      Serial.println(F("MIDI Received! Now sending"));
      MIDI.sendNoteOn(note, velocity, channel);
      beepsweep((a / 32) * pow(2, ((note - 9) / 12.0)),100,1);
      MIDI.sendNoteOff(note, velocity, channel);
      melodyPos++;
    }
  });
}
// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
void loop()
{
  // Listen to incoming notes
  MIDI.read();
  yield();
  if (WiFi.status() == WL_CONNECTED) {
    ledSwitch();
  }
 
  // send a note every second
  // (dont call delay(1000) as it will stall the pipeline)
  if ((isConnected > 0) && (millis() - t0) > 2000)
  {
    t0 = millis();
   
    byte note = melody[melodyPos%16];
    byte velocity = rand() % 63 + 63;
    byte channel = 1;
    MIDI.sendNoteOn(note, velocity, channel);
    beepsweep((a / 32) * pow(2, ((note - 9) / 12.0)),60,1);
    MIDI.sendNoteOff(note, velocity, channel);
    melodyPos++;
  }
}
</pre>
==Day 8==
===Day eight was a holy day of rest===
==Day 9==
[[File:Room-for-sound-week-2.jpg|frameless|right|alt=room-for-sound-week-2|room-for-sound-week-2]]
===Consonance and Dissonance===
One computer wants to play more consonant intervals, and the other one wants to play more dissonant intervals. They tell eachother what they played last over rtpMIDI. C++ is a pain in the hoop everything is so complicated with it. The buzzers are attempting to play the correct notes, and the MIDI is also driving two software synths on my computer.
[https://2223.mywdka.nl/roomforsound/sound/musikalische-wurfelspiel/ It sounds like this]
Then I added a little rand()omness for whether the new note is above or below the previous one. Remember randomness on a computer is not real, each ESP is deciding which notes to play through the knowledge of consonance and dissonance I gave them and their pseudorandom number generation that was programmed by someone else. They are deciding when to play based on each other, some limits set by me, and the latency created by the network.
[https://2223.mywdka.nl/roomforsound/sound/componia/ It sounds like this]
<pre>
byte intervals[7] = {0,5,2,4,3,6,1}; //this is half an octave of intervals, in order of consonance
byte lastnote = 63;
//This is a function that plays a (roughly) more dissonant note than the current interval
byte dissonate(byte note, byte lastnote){
  AM_DBG(F("The old notes were: "), note, lastnote);
  int currentInterval = min(abs(note%12 - lastnote%12), abs(lastnote%12 - note%12));
  AM_DBG(F("The old interval is: "), currentInterval);
  if (currentInterval == 1){
    AM_DBG(F("The new note is also: "), lastnote);
    return lastnote;
  }
  for (int i=0; i<7; i++) {
    if (currentInterval == intervals[i]) {
      int sign = random() & 1 ? 1 : -1;
      byte newnote = note + (sign * intervals[i+1]);
      AM_DBG(F("The new interval is: "), intervals[i+1]);
      AM_DBG(F("The new note is: "), newnote);
      return newnote;
      break;
    }
  }
}
</pre>
<pre>
//This is a function that plays a (roughly) more consonant note than the current interval
byte consonate(byte note, byte lastnote){
  AM_DBG(F("The old notes were: "), note, lastnote);
  int currentInterval = min(abs(note%12 - lastnote%12), abs(lastnote%12 - note%12));
  AM_DBG(F("The old interval is: "), currentInterval);
  if (currentInterval == 0){
    AM_DBG(F("The new note is also: "), lastnote);
    return lastnote;
  }
  for (int i=0; i<7; i++) {
    if (currentInterval == intervals[i]) {
      int sign = random() & 1 ? 1 : -1;
      byte newnote = note + (sign * intervals[i-1]);
      AM_DBG(F("The new interval is: "), intervals[i-1]);
      AM_DBG(F("The new note is: "), newnote);
      return newnote;
      break;
    }
  }
}
</pre>
==Day 10==
===where are Ü now===
Today I didnt change or develop anything new too much but doing a little review. Technically, got some good info on sending musical information on networks like Bluetooth and LAN, from Arduino libraries and different pieces of software. I tried some C++ which was terrible and I never want to do again. It's fine I guess. I'd love to connect more things to the arduinos, CV in/out to modular and buttons directly controlling the arduino, making other instruments and humans more involved. It would be nice to package that all up as a "network module" for a synth (Imagine two performances in different parts of the world sending CV to each other). Exploring audio routing over the network would let me finally try the thing with the two pianos, or maybe this can be done with MIDI too. Did the sound reveal and play the network as intended? Yes it's definitely literally doing that but I'm not sure how well it has been communicated, maybe this could be improved too. The latency rhythm thing worked better than expected, and the consonance/dissonance shows the effect in another way. Maybe it needs to be in a video format to explain this fully, or have some text describing it. Maybe [https://2223.mywdka.nl/roomforsound/sound/latent-rhythms/ the sound from Tuesday] showed this clearest, without any distraction.
I made some similar audio to yesterday, with more of me interpreting an playing along with the computers:
[https://2223.mywdka.nl/roomforsound/sound/beeep/ You can here it hear]
Ok byeeeeee

Latest revision as of 15:50, 27 January 2023

this room for sound residency will be an exploration of tying digital connections and using them as an instrument. connexion used to be spelled with an x which shows the origin of the word: a binding or joining together. opening a network connexion creates a string of data which can and will be plucked. a work in the form of a net: strings interwoven and interconnetted. starting from short strings inside a single computer, over the two weeks this network will expand to cover the entire universe.

This is a two week residency in the Willem de Kooning Academy's Room for Sound, from 16th to 27th January 2023. I will be experimenting with networked music, in particular I plan to use some ESP32 boards and a Raspberry Pi to send and receive rtpMIDI over LAN and hopefully WAN, and to translate this MIDI data to and from audio (via microphones, sensors, buzzers, speakers, etc). I would like to get other people involved in this process as well as getting the machines to interact: the machines could be not just musicians but also instruments.

Pseudocode

Get the computers to entrain to eachother dynamically, clap with eachother

While
    Countup++ per ms
    If(hearbeep) or (countup= random ~ 200ms)
        If(countup >40ms)
            Wait(countup ms)
            Sendbeep
            Countup=0

Day 1

Get the computers to sing

Today I am working with two Seeed Studios Xiao ESP32-C3 and some buzzers. Writing Arduino (C++) scripts for them to make beeps and also blinkenlights because they're fun.

/**
 * Singing 
 *
 * Plays predefined tones in a predefined order from a buzzer on pin D9.
 * Also flashes an LED on pin D8 just for fun.
 */
#include "Arduino.h"
// #include "pitches.h"

// Define some pitches of beeps, a basic time unit, a status
int dt1=200;
int dt2=90;
int dt3=360;
int dt4=400;
int duration=50;
int ledstatus=0;

void beep(int tone1,int length) {
  //this part switches the LED status
  if(ledstatus==0){
    digitalWrite(D8,HIGH);
    ledstatus=1;
  }else{
    digitalWrite(D8,LOW);
    ledstatus=0;
  }
  //This part pulses the buzzer.
   for (int tonecounter=1;tonecounter<=length;tonecounter=tonecounter+1){
     analogWrite(A0,127);
     delayMicroseconds(tone1);
     analogWrite(A0,0);
     delayMicroseconds(tone1);
   }
  //Or you could for more exact tones and lengths but no analog
  //tone(D9, tone1, length);
}

void setup()
{
  // initialize LED digital pin as an output.
  pinMode(D8, OUTPUT);
  pinMode(A0, OUTPUT);
}

void loop()
{
  //A sequence of buzzes
  beep(dt1,duration*2);
  beep(dt2,duration);
  beep(dt1,duration*2);
  beep(dt2,duration*2);
  beep(dt1,duration);
  beep(dt2,duration*4);
  beep(dt1,duration*2);
  beep(dt2,duration);
  beep(dt1,duration*2);
  beep(dt3,duration);
  beep(dt1,duration*2);
  beep(dt4,duration*4);
}

Someone else sending data from arduino xto ChucK

Get the computers to clap

Some tweaks to that code (tweaks only below) gave me two buzzers that beep at predefined tempi (metronomes I guess). They can make nice polyrhythms together. Timing is tricky!

int notelength=20;
int bpm = 100;
unsigned long delay1=(60000000/bpm) - (1000 * notelength);

void clap(int tone1,int bpm) {
  tone(A0, tone1, notelength);
  delayMicroseconds(delay1);
}

void loop()
{
  //A clapping speed
  clap(dt3,180);
}

How do you embed audio on the wiki? Here's a link instead.

Day 2

Get the computers to listen

To sound or to digital signals? If they're digital I probably need them to listen more generally to wifi or bluetooth first. But I dont have any microphones for the C3's today so if its audio input I'll need to use another board.

The ESP32-A1S: Audio Inputs

How to work this board. First I am following the instructions here. Installing ESP-IDF and ESP-ADF. (IoT and Audio Development Frameworks). Actually I'm going to give up on this and use Arduino. I found a video tutorial and a library that look good. Hmm this board is actually quite tricky to get going. I have installed another library to work with this exact board but failing to upload after compile (A fatal error occurred: Could not open COM70, the port doesn't exist). I have been experimenting with the platformio.ini file below but still no luck. I think I'll give up on this part for today.

; PlatformIO Project Configuration File for arduino-audiokit-hal
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps = https://github.com/pschatzmann/arduino-audiokit-hal

	c:\Users\user\Downloads\arduino-audio-tools-main.zip
lib_ldf_mode = deep+
build_flags = -DCORE_DEBUG_LEVEL=5 -DAUDIOKIT_BOARD=5 
monitor_speed = 115200

(Also this serial plotter looks cool)

Explore timbre

I didnt expect to be so interested in the tone of the buzzers but they are very cool. The digitalWrite function (and analogWrite, and delay) seem interesting to explore further to see what happens. Can you keep a constant pitch and change tone to explore what is possible? Two first attempts at playing with tone by varying the pulse length and gap:

void beepdown(int tone1,int length) {
  //This part pulses the buzzer.
  for (int tonecounter=1;tonecounter<=length;tonecounter=tonecounter+1){
    analogWrite(A0,250);
    delayMicroseconds(tone1*((length-tonecounter+1)/10));
    analogWrite(A0,0);
    delayMicroseconds(tone1*(tonecounter/10));
  }
}
void beepup(int tone1,int length) {
  //This part pulses the buzzer.
  for (int tonecounter=1;tonecounter<=length;tonecounter=tonecounter+1){
    analogWrite(A0,250);
    delayMicroseconds(tone1*(tonecounter/10));
    analogWrite(A0,0);
    delayMicroseconds(tone1*((length-tonecounter+1)/10));
  }
}

They came out very squealy and uncomfortable sounding in the recording, but interesting tones for sure.

Listen here

Day 3

ESP32C3 Listening

Ok back to the ESP32-C3, today I want to get WiFi or Bluetooth working on these, maybe sending MIDI data over these channels, bluetooth first because I'm scared of the network here (although the ESP32 could be a wifi access point which may be interesting too). I'm kinda following this tutorial. First I copied a bluetooth scanner that prints to the serial port. Success! Then a different script that sets it as a bluetooth server, and I managed to send some data (text) from my phone. Now I'll tweak this and see if the ESP will play a sound when my phone sends data. Yesssss it does indeed.

/**
 * Sing for me 
 *
 * Plays tones from a buzzer on pin A0 when bluetooth commands are received.
 * Also flashes an LED on pin D8 just for fun.
 */

#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
 
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
 
#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

// Define some pitches of beeps, a basic time unit, a status
int dt1=400;
int dt2=360;
int dt3=480;
int dt4=400;
int dt5=960;
int dt6=200;
int duration=50;
int ledstatus=0;
int notelength=14;
int bpm = 240;
unsigned long delay1=(60000000/bpm) - (1000 * notelength); 
 
 
// functions for making sounds

void ledSwitch(){
  //this part switches the LED status
  if(ledstatus==0){
    digitalWrite(D8,HIGH);
    ledstatus=1;
  }else{
    digitalWrite(D8,LOW);
    ledstatus=0;
  }
}

void beep(int tone1,int length) {
  ledSwitch();
  //This part pulses the buzzer.
  for (int tonecounter=1;tonecounter<=length;tonecounter=tonecounter+1){
    analogWrite(A0,250);
    delayMicroseconds(tone1);
    analogWrite(A0,0);
    delayMicroseconds(tone1);
  }
}

void clap(int tone1) {
  tone(A0, tone1, notelength);
  delayMicroseconds(delay1);
}

void beepsweep(int tone1,int length, int direction) {
  ledSwitch();
  //This part pulses the buzzer.
  for (int tonecounter=1;tonecounter<=length;tonecounter=tonecounter+1){
    analogWrite(A0,250);
    delayMicroseconds(tone1*(abs((1-direction)*length-tonecounter+1)/10));
    analogWrite(A0,0);
    delayMicroseconds(tone1*(tonecounter/10));
    delayMicroseconds(tone1*(abs(direction*length-tonecounter+1)/10));
  }
  ledSwitch();
}

// Class for BLE interaction

class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      std::string value = pCharacteristic->getValue();
 
      if (value.length() > 0) {
        Serial.println("*********");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);
        Serial.println();
        Serial.println("*********");

        beepsweep(stoi(value),duration*2, 1);
      }
    }
};


 
void setup() {
  Serial.begin(115200);

  // initialize LED digital pin as an output, and buzzer analog pin.
  pinMode(D8, OUTPUT);
  pinMode(A0, OUTPUT);
 
  BLEDevice::init("Bzzzzzzzt");
  BLEServer *pServer = BLEDevice::createServer();
 
  BLEService *pService = pServer->createService(SERVICE_UUID);
 
  BLECharacteristic *pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE
                                       );
 
  pCharacteristic->setCallbacks(new MyCallbacks());
 
  pCharacteristic->setValue("Hello World");
  pService->start();
 
  BLEAdvertising *pAdvertising = pServer->getAdvertising();
  pAdvertising->start();
}
 
void loop() {
  // put your main code here, to run repeatedly:
  delay(2000);
}
A buzzer taped to a window

I also strapped a little flat buzzer to the window and sent bluetooth notes to it from across the building. Couldnt get it to work from the building next door though, is it because of the wind? Or the windowpane? Or the windy staircase? Anyway strapping it to the window is very interesting, these buzzers are really weak and the sound comes through so quietly I really like it.

Here's the sound

So that works great for BT signals in and serial out but I need to introduce the two machines to eachother, so make a connection and send BLE messages.

Wifey

a wifi access point on a micro controller

Tutorial I'm trying. So first I'm making one ESP32C3 a wifi access point (like a hotspot on a phone). It works and I can connect to it on my phone or laptop. Now I'll try to set up the other module to connect to this network.

Day 4

La La LAN

la la lan

Today: connect ESP32-C3s directly to eachother. On Wifi. On bluetooth. Lets try WiFi first, still using yesterday's tutorial. Connecting to the other ESP32 network: all works good. I also set up the connecting chip to have a webserver that turns on an LED, and plays a buzzer.

Here's the sound

Also managed to connect to eduroam. No cert needed here, just login credentials. So I can connect both ESPs to the eduroam wifi at the same time. I can ping them and have given them Wifi status LED's that flash if WiFi is connected. But I cant get a webserver running on eduroam hmmm. Maybe I should try to open a port, or maybe it wont be possible to use this network.

Now they need to have a little chat with eachother.

Day 5

room-for-sound-week-1

The day I get the ESP32 to send more interesting data

This playlist gives me some information on how to MIDI on Arduino. But actually that is for MIDI over DIN, so maybe for a different day. This library does lots of MIDI stuff including MIDI over serial and BLE, and can later be expanded with this to do rtpMIDI.

Also tried MIDIUSB where I learnt that an ESP32 does not in fact have USB communication, rather a serial port going down a USB wire. Interessant.

First I'm getting the ESP32 to send MIDI over BLE, using this script and this BLE MIDI driver. The code below sends a 150bpm drum pattern over MIDI, while playing a melody itself on a buzzer. I really like how the synchronisation is all messed up, this is what I was hoping for. Next week the plan is to play with this effect some more and get it happeing on different networks. I am working with microsecond delays for the moment, but this is probably not the most accutate way to count time. Or maybe it is. The processor is 160Mhz so I'm not sure if I need to account for that, more likely I think it is to do with bluetooth latency (does bluetooth MIDI have strict timing? I doubt it).

It sounds like this.

#include <Arduino.h>
#include <BLEMIDI_Transport.h>
#include <hardware/BLEMIDI_ESP32.h>
BLEMIDI_CREATE_INSTANCE("Bammm",MIDI);
  
// Define some pitches of beeps, a basic time unit, a status
int melody[16] = {400, 360, 480, 400, 960, 200, 400, 360, 480, 400, 960, 200, 400, 360, 480, 400};
int melodyPos = 0;
int ledstatus=0;
int tempo = 100000; //a 16th note in 150bpm
 
// functions for making sounds

void ledSwitch(){
  //this part switches the LED status
  if(ledstatus==0){
    digitalWrite(D8,HIGH);
    ledstatus=1;
  }else{
    digitalWrite(D8,LOW);
    ledstatus=0;
  }
}

void clap(int tone1) {
  ledSwitch();
  tone(A0, tone1, 100);
  delayMicroseconds(tempo-10000);
  ledSwitch();
}

void setup()
{
  // initialize LED digital pin as an output, and buzzer analog pin.
  pinMode(D8, OUTPUT);
  pinMode(A0, OUTPUT);

  MIDI.begin(10);
}

void loop()
{
    for (int i =0; i<16; i++){
      if((i%4==0)||(i==14)){
        MIDI.sendNoteOn(36, 100, 10);
        MIDI.sendNoteOff(36, 0, 10);
      }
      MIDI.sendNoteOn(44, 100, 10);
      MIDI.sendNoteOff(44, 0, 10);
      if(i%3==0){
        clap(melody[melodyPos%16]);
        melodyPos++;
      }else{
        delayMicroseconds(tempo); 
      }
    }
}

Next week: rtpMIDI, receiving MIDI data to make sounds on the ESP, buttons, two ESPs communicating (via some sort of MIDI).

Day 6

The day that rtpMIDI finally happens

Today is the day. This library let me send MIDI on the internet. I also used this program to do rtpMIDI on windows. So I am sending rtpMIDI over a wifi port and tomorrow I should finally be able to build the program from the pseudocode at the start wooo.

Listen to it here.

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <WiFiUdp.h>

#define SerialMon Serial
#include <AppleMIDI.h>


// #-------------------------------eduroam stuff------------------

#include "esp_wpa2.h" //wpa2 library for connections to Enterprise networks

//Identity for user with password related to his realm (organization)
//Available option of anonymous identity for federation of RADIUS servers or 1st Domain RADIUS servers

#define EAP_ANONYMOUS_IDENTITY "**********" //anonymous@example.com, or you can use also nickname@example.com
#define EAP_IDENTITY "***********" //nickname@example.com, at some organizations should work nickname only without realm, but it is not recommended
#define EAP_PASSWORD "********" //password for eduroam account

//SSID NAME
const char* ssid = "eduroam"; // eduroam SSID
int ledstatus=0;

void ledSwitch(){
  //this part switches the LED status
  if(ledstatus==0){
    digitalWrite(D8,HIGH);
    ledstatus=1;
  }else{
    digitalWrite(D8,LOW);
    ledstatus=0;
  }
}

void beepsweep(int tone1,int length, int direction) {
  ledSwitch();
  //This part pulses the buzzer.
  for (int tonecounter=1;tonecounter<=length;tonecounter=tonecounter+1){
    analogWrite(A0,250);
    delayMicroseconds(tone1*(abs((1-direction)*length-tonecounter+1)/10));
    analogWrite(A0,0);
    delayMicroseconds(tone1*(tonecounter/10));
    delayMicroseconds(tone1*(abs(direction*length-tonecounter+1)/10));
  }
  ledSwitch();
}

unsigned long t0 = millis();
int8_t isConnected = 0;

APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE();

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
void setup()
{
  AM_DBG_SETUP(115200);
  delay(3000);
  AM_DBG(F("Booting"));

  pinMode(D8, OUTPUT);
  pinMode(A0, OUTPUT);
  // Serial.begin(115200);

  Serial.print(F("Connecting to network: "));
  Serial.println(ssid);
  WiFi.disconnect(true);  //disconnect form wifi to set new wifi connection
  WiFi.mode(WIFI_STA); //init wifi mode
  WiFi.begin(ssid, WPA2_AUTH_PEAP, EAP_ANONYMOUS_IDENTITY, EAP_IDENTITY, EAP_PASSWORD); //without CERTIFICATE
  // while (WiFi.status() != WL_CONNECTED) {
  //   delay(500);
  //   Serial.print(F("."));
  // }
  // Serial.println("");
  // Serial.println(F("WiFi is connected!"));
  // Serial.println(F("IP address set: "));
  // Serial.println(WiFi.localIP()); //print LAN IP
  // ledSwitch();

  // WiFi.begin(ssid, pass);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    AM_DBG(F("Establishing connection to WiFi.."));
  }
  AM_DBG(F("Connected to network"));
  ledSwitch();

  AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled"));
  AM_DBG(F("Add device named Arduino with Host"), WiFi.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")");
  AM_DBG(F("Select and then press the Connect button"));
  AM_DBG(F("Then open a MIDI listener and monitor incoming notes"));
  AM_DBG(F("Listen to incoming MIDI commands"));

  MIDI.begin();

  AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) {
    isConnected++;
    AM_DBG(F("Connected to session"), ssrc, name);
  });
  AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) {
    isConnected--;
    AM_DBG(F("Disconnected"), ssrc);
  });
  
  MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) {
    AM_DBG(F("NoteOn"), note);
  });
  MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) {
    AM_DBG(F("NoteOff"), note);
  });

  AM_DBG(F("Sending NoteOn/Off of note 45, every second"));
}

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
void loop()
{
  // Listen to incoming notes
  MIDI.read();

  yield();
  if (WiFi.status() == WL_CONNECTED) {
    delay(4000);
    Serial.println("");
    Serial.println(F("WiFi is connected!"));
    Serial.println(F("IP address set: "));
    Serial.println(WiFi.localIP()); //print LAN IP
    AM_DBG(F("Add device named Arduino with Host"), WiFi.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")");
    ledSwitch();
  }
  
  // send a note every second
  // (dont cáll delay(1000) as it will stall the pipeline)
  if ((isConnected > 0) && (millis() - t0) > 1000)
  {
    t0 = millis();

    byte note = 45;
    byte velocity = 55;
    byte channel = 1;

    Serial.println(F("MIDI Sending"));
    MIDI.sendNoteOn(note, velocity, channel);
    MIDI.sendNoteOff(note, velocity, channel);
    beepsweep(600,120,1);
  }
}

And I also received MIDI on the ESPs

That was pretty straightforward.

Day 7

Latent Rhythms

Today I sent rtpMIDI over the internet from one ESP32 to another, via some routing on my computer. The code below is one of a pair of programs that plays a beep after it receives a MIDI note, and sends a MIDI note back. If it doesnt hear a note at all for 2000ms, it beeps anyway. The latency was way more irregular than I expected making some really interesting rhythms. You can hear the network, and the network itself is making the music interesting not the individual performers.

It sounds like this

Other ways to use this

What happens if there are more than two devices

What else can the MIDI control

What happens if replies send in the next MIDI channel up, but resets always send in channel one.

Can the 2000ms reset be adapted: trusting the other performers more and more if the latency is low.

Can you send CV gate from MIDI in using these ESP32s

What happens if a modular synth gets involved

Can it work on WAN as well as LAN

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <WiFiUdp.h>
#define SerialMon Serial
#include <AppleMIDI.h>


// #-------------------------------eduroam stuff------------------

#include "esp_wpa2.h" //wpa2 library for connections to Enterprise networks
//Identity for user with password related to his realm (organization)
#define EAP_ANONYMOUS_IDENTITY "????????????????" //anonymous@example.com, or you can use also nickname@example.com
#define EAP_IDENTITY "!!!!!!!!!!!!!!" //nickname@example.com, at some organizations should work nickname only without realm, but it is not recommended
#define EAP_PASSWORD "££££££££££££" //password for eduroam account
const char* ssid = "$$$$$$$$$$$"; // eduroam SSID

// Other variables
int ledstatus=0;
int melodyPos=0;
int melody[16] = {63, 66, 65, 68, 65, 62, 61, 63, 65, 63, 57, 61, 63, 66, 65, 62};
float a = 660;

unsigned long t0 = millis();
int8_t isConnected = 0;

APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE();


// Functions
// #-------------------------------------

void ledSwitch(){
  //this part switches the LED status
  if(ledstatus==0){
    digitalWrite(D8,HIGH);
    ledstatus=1;
  }else{
    digitalWrite(D8,LOW);
    ledstatus=0;
  }
}

void beepsweep(int tone1,int length, int direction) {
  long delay = (500000/tone1);
  ledSwitch();
  AM_DBG(F("Delay in microseconds is "), delay);
  //This part pulses the buzzer.
  for (int tonecounter=1;tonecounter<=length;tonecounter=tonecounter+1){
    analogWrite(A0,250);
    delayMicroseconds(delay);
    analogWrite(A0,0);
    delayMicroseconds(delay);
  }
  ledSwitch();
}

// Running stuff
// #-------------------------------------------------------------------------
void setup()
{
  AM_DBG_SETUP(115200);
  delay(3000);
  AM_DBG(F("Booting"));

  pinMode(D8, OUTPUT);
  pinMode(A0, OUTPUT);
  // Serial.begin(115200);

  Serial.print(F("Connecting to network: "));
  Serial.println(ssid);
  WiFi.disconnect(true);  //disconnect form wifi to set new wifi connection
  WiFi.mode(WIFI_STA); //init wifi mode
  WiFi.begin(ssid, WPA2_AUTH_PEAP, EAP_ANONYMOUS_IDENTITY, EAP_IDENTITY, EAP_PASSWORD); //without CERTIFICATE

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    AM_DBG(F("Establishing connection to WiFi.."));
  }
  AM_DBG(F("Connected to network"));
  ledSwitch();

  AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled"));
  AM_DBG(F("Add device named Arduino with Host"), WiFi.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")");
  AM_DBG(F("Select and then press the Connect button"));
  AM_DBG(F("Then open a MIDI listener and monitor incoming notes"));
  AM_DBG(F("Listen to incoming MIDI commands"));

  MIDI.begin();

  AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) {
    isConnected++;
    AM_DBG(F("Connected to session"), ssrc, name);
  });
  AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) {
    isConnected--;
    AM_DBG(F("Disconnected"), ssrc);
  });
  
  MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) {
    if (channel == 3){
      t0 = millis();
      
      byte note = melody[melodyPos%16];
      byte velocity = rand() % 63 + 63;
      byte channel = 1;

      Serial.println(F("MIDI Received! Now sending"));
      MIDI.sendNoteOn(note, velocity, channel);
      beepsweep((a / 32) * pow(2, ((note - 9) / 12.0)),100,1);
      MIDI.sendNoteOff(note, velocity, channel);

      melodyPos++;
    }
  });
}

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
void loop()
{
  // Listen to incoming notes
  MIDI.read();

  yield();
  if (WiFi.status() == WL_CONNECTED) {
    ledSwitch();
  }
  
  // send a note every second
  // (dont call delay(1000) as it will stall the pipeline)
  if ((isConnected > 0) && (millis() - t0) > 2000)
  {
    t0 = millis();
    
    byte note = melody[melodyPos%16];
    byte velocity = rand() % 63 + 63;
    byte channel = 1;

    MIDI.sendNoteOn(note, velocity, channel);
    beepsweep((a / 32) * pow(2, ((note - 9) / 12.0)),60,1);
    MIDI.sendNoteOff(note, velocity, channel);

    melodyPos++;
  }
}

Day 8

Day eight was a holy day of rest

Day 9

room-for-sound-week-2

Consonance and Dissonance

One computer wants to play more consonant intervals, and the other one wants to play more dissonant intervals. They tell eachother what they played last over rtpMIDI. C++ is a pain in the hoop everything is so complicated with it. The buzzers are attempting to play the correct notes, and the MIDI is also driving two software synths on my computer.

It sounds like this

Then I added a little rand()omness for whether the new note is above or below the previous one. Remember randomness on a computer is not real, each ESP is deciding which notes to play through the knowledge of consonance and dissonance I gave them and their pseudorandom number generation that was programmed by someone else. They are deciding when to play based on each other, some limits set by me, and the latency created by the network.

It sounds like this

byte intervals[7] = {0,5,2,4,3,6,1}; //this is half an octave of intervals, in order of consonance
byte lastnote = 63;


//This is a function that plays a (roughly) more dissonant note than the current interval
byte dissonate(byte note, byte lastnote){
  AM_DBG(F("The old notes were: "), note, lastnote);
  int currentInterval = min(abs(note%12 - lastnote%12), abs(lastnote%12 - note%12));
  AM_DBG(F("The old interval is: "), currentInterval);
  if (currentInterval == 1){
    AM_DBG(F("The new note is also: "), lastnote);
    return lastnote;
  }
  for (int i=0; i<7; i++) {
    if (currentInterval == intervals[i]) {
      int sign = random() & 1 ? 1 : -1;
      byte newnote = note + (sign * intervals[i+1]);
      AM_DBG(F("The new interval is: "), intervals[i+1]);
      AM_DBG(F("The new note is: "), newnote);
      return newnote;
      break;
    }
  }
}
//This is a function that plays a (roughly) more consonant note than the current interval
byte consonate(byte note, byte lastnote){
  AM_DBG(F("The old notes were: "), note, lastnote);
  int currentInterval = min(abs(note%12 - lastnote%12), abs(lastnote%12 - note%12));
  AM_DBG(F("The old interval is: "), currentInterval);
  if (currentInterval == 0){
    AM_DBG(F("The new note is also: "), lastnote);
    return lastnote;
  }
  for (int i=0; i<7; i++) {
    if (currentInterval == intervals[i]) {
      int sign = random() & 1 ? 1 : -1;
      byte newnote = note + (sign * intervals[i-1]);
      AM_DBG(F("The new interval is: "), intervals[i-1]);
      AM_DBG(F("The new note is: "), newnote);
      return newnote;
      break;
    }
  }
}

Day 10

where are Ü now

Today I didnt change or develop anything new too much but doing a little review. Technically, got some good info on sending musical information on networks like Bluetooth and LAN, from Arduino libraries and different pieces of software. I tried some C++ which was terrible and I never want to do again. It's fine I guess. I'd love to connect more things to the arduinos, CV in/out to modular and buttons directly controlling the arduino, making other instruments and humans more involved. It would be nice to package that all up as a "network module" for a synth (Imagine two performances in different parts of the world sending CV to each other). Exploring audio routing over the network would let me finally try the thing with the two pianos, or maybe this can be done with MIDI too. Did the sound reveal and play the network as intended? Yes it's definitely literally doing that but I'm not sure how well it has been communicated, maybe this could be improved too. The latency rhythm thing worked better than expected, and the consonance/dissonance shows the effect in another way. Maybe it needs to be in a video format to explain this fully, or have some text describing it. Maybe the sound from Tuesday showed this clearest, without any distraction.

I made some similar audio to yesterday, with more of me interpreting an playing along with the computers:

You can here it hear

Ok byeeeeee