Sıla KaraFrancesco Mignogna
Published

StepScape

A device that gradually adds musical instruments and shifts the colors toward warmer tones as people take steps, encouraging them to develop

IntermediateWork in progress3 days32
StepScape

Things used in this project

Hardware components

Barduino 4.0.2
×2

Software apps and online services

Arduino IDE
Arduino IDE
touchdesigner
moises

Hand tools and fabrication machines

10 Pc. Jumper Wire Kit, 5 cm Long
10 Pc. Jumper Wire Kit, 5 cm Long
sewing machine
fabrics
power bank
LSM6DSOX
led

Story

Read more

Custom parts and enclosures

touchdesigner

This is the network of nodes created in TouchDesigner to receive the step data in real time (for this, only one OSC In CHOP is needed). We set a rule where every 50 steps the count1 node increases by 1. Each time count1 receives this increment, it sends a signal to the CHOP Execute DAT, which, through the code inside it, activates an Audio File In CHOP. In my network, the Audio File In CHOPs represent the different instruments. When they are all active, all the instruments and vocals of the selected song can be heard.

For example: after the first 50 steps the bass will play; at 100 steps the piano will be added; at 150 steps the vocals will enter; and at 200 steps the drums will also start. In this way, by walking, the song is progressively assembled

Schematics

Breadboard connections

Breadboard connections for the led

Code

code for the step counter

Arduino
This code connects the chip to a Wi-Fi network. The chip must be connected to the same network as the computer being used, activates the step counter, and sends the results in real time to TouchDesigner via OSC. Once 50 steps are reached, it also sends a signal to a second Arduino to change the LED color in real time.
#include <Wire.h>
#include <math.h>
#include <stdint.h>

#include <Adafruit_Sensor.h>
#include <Adafruit_LSM6DSOX.h>

#include <WiFi.h>
#include <WiFiUdp.h>
#include <OSCMessage.h>

#include <WebServer.h>

// ================= WIFI =================
const char* ssid     = "Iaac-Wifi";
const char* password = "EnterIaac22@";

// ================= TOUCHDESIGNER =================
const char* PC_IP = "172.16.22.112";
const int PC_OSC_PORT = 9000;

// ================= LED CONTROLLER =================
const char* LED_IP = "172.16.20.37";
const int LED_PORT = 7777;

// ================= UDP =================
const int LOCAL_UDP_PORT = 8888;
WiFiUDP Udp;

// ================= SENSOR =================
Adafruit_LSM6DSOX sox;

// ================= HTTP =================
WebServer server(80);

// ================= STEP COUNTER =================
float threshold = 12.0;
unsigned long debounceMs = 250;

int stepCount = 0;
bool aboveThreshold = false;
unsigned long lastStepTime = 0;

// ================= OSC SEND TOUCHDESIGNER =================
unsigned long lastSend = 0;
int lastSentSteps = -1;
unsigned long minSendIntervalMs = 50;

// ================= LED LEVEL =================
int lastSentLevel = -1;

void handleRoot() {
  server.send(200, "text/plain", String(stepCount));
}

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

  // I2C
  Wire.begin(1, 2);

  if (!sox.begin_I2C()) {
    Serial.println("LSM6DSOX not found!");
    while (1) delay(10);
  }

  sox.setAccelRange(LSM6DS_ACCEL_RANGE_4_G);
  sox.setAccelDataRate(LSM6DS_RATE_26_HZ);

  // ===== WIFI =====
  WiFi.disconnect(true);
  delay(1000);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  Serial.print("Connecting to WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(400);
    Serial.print(".");
  }

  Serial.println();
  Serial.println("WiFi connected!");
  Serial.print("ESP32 step counter IP: ");
  Serial.println(WiFi.localIP());

  // UDP
  Udp.begin(LOCAL_UDP_PORT);

  // HTTP server
  server.on("/", handleRoot);
  server.begin();
}

void loop() {
  server.handleClient();

  sensors_event_t accel, gyro, temp;
  sox.getEvent(&accel, &gyro, &temp);

  float ax = accel.acceleration.x;
  float ay = accel.acceleration.y;
  float az = accel.acceleration.z;

  float magnitude = sqrtf(ax * ax + ay * ay + az * az);
  unsigned long now = millis();

  // ===== STEP DETECTION =====
  if (!aboveThreshold && magnitude > threshold) {
    aboveThreshold = true;

    if (now - lastStepTime > debounceMs) {
      stepCount++;
      lastStepTime = now;

      Serial.print("Step: ");
      Serial.println(stepCount);
    }
  }

  if (aboveThreshold && magnitude < (threshold * 0.85f)) {
    aboveThreshold = false;
  }

  // ===== SEND OSC TO TOUCHDESIGNER =====
  if (stepCount != lastSentSteps && (now - lastSend) > minSendIntervalMs) {
    OSCMessage msg("/steps");
    msg.add((int32_t)stepCount);

    Udp.beginPacket(PC_IP, PC_OSC_PORT);
    msg.send(Udp);
    Udp.endPacket();
    msg.empty();

    lastSend = now;
    lastSentSteps = stepCount;
  }

  // ===== CALCOLO LIVELLO COLORE =====
  int currentLevel = 0;

  if (stepCount < 50) {
    currentLevel = 0;   // blu
  } else if (stepCount < 100) {
    currentLevel = 1;   // viola
  } else if (stepCount < 150) {
    currentLevel = 2;   // rosa
  } else if (stepCount < 200) {
    currentLevel = 3;   // arancione
  } else {
    currentLevel = 4;   // rosso
  }

  // ===== SEND OSC TO LED CONTROLLER =====
  if (currentLevel != lastSentLevel) {
    OSCMessage ledMsg("/ledLevel");
    ledMsg.add((int32_t)currentLevel);

    Udp.beginPacket(LED_IP, LED_PORT);
    ledMsg.send(Udp);
    Udp.endPacket();
    ledMsg.empty();

    lastSentLevel = currentLevel;

    Serial.print("LED level sent: ");
    Serial.println(currentLevel);
  }

  delay(5);
}

code for CHOP Execute DAT

Python
with this code each time the CHOP Execute DAT receives an input, it sends a signal that activates only one Audio File In at a time
# CHOP Execute DAT (chopexec1)
# CHOPs: count1
# Value Change: ON

STEMS = ['other', 'vocals', 'drums', 'bass']   # Audio File In (nomi esatti)
MATHS = ['math3', 'math4', 'math5', 'math6']   # Math CHOP gate (nomi esatti)
MAX_LAYERS = 4

started = False


def _find_and_set_multiply(opm, v):
    """
    Trova il parametro Multiply dentro un Math CHOP e lo imposta.
    Funziona anche se il nome interno non è 'mult'.
    """
    if opm is None:
        debug('ERRORE: Math non trovato')
        return False

    # prova nomi comuni
    for pname in ('mult', 'multiply', 'mul'):
        try:
            p = getattr(opm.par, pname)
            p.val = v
            return True
        except:
            pass

    # fallback: cerca un parametro con label "Multiply" o nome che contiene "mult"
    try:
        for p in opm.pars():
            lbl = (str(p.label) or '').strip().lower()
            nam = (str(p.name) or '').strip().lower()
            if lbl == 'multiply' or 'mult' in nam:
                p.val = v
                return True
    except:
        pass

    debug('ERRORE: non trovo Multiply su', opm.path)
    return False


def _stop_all():
    for n in STEMS:
        a = op(n)
        if a is None:
            continue
        if hasattr(a.par, 'play'):
            a.par.play = 0


def _start_all_from_zero():
    # allinea tutti i file audio allo stesso punto e parte insieme
    for n in STEMS:
        a = op(n)
        if a is None:
            debug('WARNING: stem non trovato:', n)
            continue

        if hasattr(a.par, 'cuepoint'):
            a.par.cuepoint = 0

        if hasattr(a.par, 'cue'):
            try:
                a.par.cue.pulse()
            except:
                pass

        if hasattr(a.par, 'play'):
            a.par.play = 1


def _set_layers(layer_count):
    """
    layer_count:
      0 -> tutti OFF
      1 -> math3 ON
      2 -> math3+math4 ON
      3 -> +math5 ON
      4 -> +math6 ON
    """
    layer_count = max(0, min(MAX_LAYERS, int(layer_count)))

    report = []
    for i, mname in enumerate(MATHS):
        opm = op(mname)
        gate = 1 if layer_count >= (i + 1) else 0
        ok = _find_and_set_multiply(opm, gate)
        report.append((mname, gate, ok))

    debug('LAYERS =', layer_count, '->', report)


def onValueChange(channel, sampleIndex, val, prev):
    global started

    # count1 è già layer (1 ogni 200 passi)
    try:
        s = int(val)
    except:
        s = int(float(val))

    debug('EVENT: count1 =', s)

    # reset
    if s <= 0:
        started = False
        _stop_all()
        _set_layers(0)
        debug('RESET DONE')
        return

    # start al primo layer
    if (not started) and (s >= 1):
        started = True
        _start_all_from_zero()
        debug('START DONE (tutti gli stem ripartiti da 0)')

    # aggiorna i gate
    _set_layers(s)
    return

code for the led

Arduino
This code is used to activate the LEDs and receives, via Wi-Fi, the signal from the step counter to change their color.
#include <WiFi.h>
#include <WiFiUdp.h>
#include <OSCMessage.h>
#include <Adafruit_NeoPixel.h>

// ===== WIFI =====
const char* ssid = "Iaac-Wifi";
const char* password = "EnterIaac22@";

// ===== UDP =====
WiFiUDP Udp;
const int LOCAL_PORT = 7777;

// ===== LED =====
#define LED_PIN 6
#define LED_COUNT 60

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

int currentColor = -1;

void setColor(int r, int g, int b) {
  for (int i = 0; i < LED_COUNT; i++) {
    strip.setPixelColor(i, strip.Color(r, g, b));
  }
  strip.show();
}

void setColorByLevel(int level) {
  switch (level) {
    case 0: setColor(0, 0, 255); break;       // blu
    case 1: setColor(128, 0, 128); break;     // viola
    case 2: setColor(255, 105, 180); break;   // rosa
    case 3: setColor(255, 165, 0); break;     // arancione
    case 4: setColor(255, 0, 0); break;       // rosso
  }
}

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

  strip.begin();
  strip.show();
  setColor(0, 0, 255);   // blu iniziale

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  Serial.print("Connecting to WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(400);
    Serial.print(".");
  }

  Serial.println();
  Serial.println("WiFi connected!");
  Serial.print("ESP32 LED IP: ");
  Serial.println(WiFi.localIP());

  Udp.begin(LOCAL_PORT);
  Serial.print("Listening UDP on port: ");
  Serial.println(LOCAL_PORT);
}

void loop() {
  OSCMessage msg;
  int packetSize = Udp.parsePacket();

  if (packetSize > 0) {
    while (packetSize--) {
      msg.fill(Udp.read());
    }

    if (!msg.hasError() && msg.fullMatch("/ledLevel")) {
      int level = msg.getInt(0);

      if (level < 0) level = 0;
      if (level > 4) level = 4;

      if (level != currentColor) {
        currentColor = level;
        setColorByLevel(level);

        Serial.print("Received level: ");
        Serial.println(level);
      }
    }
  }
}

Credits

Sıla Kara
2 projects • 1 follower
Francesco Mignogna
2 projects • 1 follower

Comments