Posted on December 13, 2024 by Tamirlan Zhairbay
Instructor: Andy
Me and Chris thought about a lot of ideas for our final project but with feedback of professor Andy, we came up with this project which we called "The Sonic Pentagon." The main factor was that our interactive method worked well with the way that touching and spatial movements facilitated interactions. The project was to design kinetic wearables, which was inspired by interactive installations and in line with some of our previous assignments. We put the knowledge we gained from that project to use, especially the responsive feedback and intuitive input. We focused on using music and graphics to create an immersive experience through feedback mechanisms in our prior study. We accomplished this by utilizing sound oscillators and ultrasonic sensors, which worked in tandem to transform physical motions into sound modulation in real time. Because a pentagonal frame made sure that sensors were equally spaced from each user interface location, this was particularly important. This makes it easier to experiment artistically and intuitively with music.
Using user testing to improve our design We learned from speaking with users that the location of sensors significantly affected usage. According on early comments, sound transitions should not be abrupt and LED feedback should be easier to understand. In response, we improved the sound synthesis algorithms to provide more resonant noises, redesigned the sensors for more sensitivity, and modified the LEDs' response mechanism and user interface to better mimic behavior. The changes produced a more natural interaction loop. Also, improved the users overall experience. We were able to develop a project that truly met the goals of integrating art, technology, and human interaction into a smooth experimental tool. We did it by focusing on how users naturally used the prototype.
Electronic Integration: Rotary encoder, NeoPixel LED strip, five ultrasonic sensors were all part of the electronic system. Every element was essential to reaching the project's overall objectives.
Ultrasonic Sensors: Using ultrasonic sensors, distance detection was made sure allowing lights and sounds to be activated in response to a users activity. Because of their short range infrared sensors were taken into consideration but we did not use them at the end.
NeoPixel LED Strip: Changed color in response to active sensors to provide visual feedback. Because they needed more intricate wiring and didn't provide the same degree of programmability, standard RGB LEDs were not chosen.
Rotary Encoder: Enabled sound and background color switching and pitch adjustments.
Mechanical Design: Flexible blackboards, which are lightweight and flexible, were used to form the structure frame. The ultrasonic sensors were easily positioned uniformly thanks to the pentagonal structure, which also provided ample space for neat wiring and modifications. After being hot glued into position we made sure that each sensor was held to provide the best possible signal readings. Despite the possibility of using alternative materials like wood or acrylic we came with the flexible option because to its ease of assembly and adaptability for prototyping.
Ultrasonic Sensors Failure: After connecting all 5 ultrasonic sensors and testing them there was some issues. The wires were too long and everything tangled up. Clearing the wires and using matching color wires for different sensors resolved the issue.
Signal Interference: Initially ultrasonic sensors interfered with each other. It was resolved by timing triggers in the Arduino code and spacing sensors further apart.
Overlapping Frequency Ranges: False positives were produced by overlapping frequency ranges(from several sensors working in close proximity). This was solved by employing alternative modulation techniques or separating frequencies.
LED Timing Issues: Early prototype struggled with syncing LED feedback to sensor inputs. Debugging the Arduino code resolved the issue
Sound Quality Issues: The technology was less interesting in its early prototype. Because the sound oscillators produced distorted or ugly tones. This issue was fixed by adding audio filters and fine-tuning the oscillators.
Arduino Code
The basic Arduino code manages connection with Processing, NeoPixel control, and distance detection. Reading sensor distances and sending data to processing are the primary tasks.
Key part of Arduino Code: Read Ultrasonic Sensors and Send data
This section calculates the distances for every sensor and sends the results to Processing as a string with commas between the values
void loop() {
for (int i = 0; i < numberOfSensors; i++) {
digitalWrite(triggerPins[i], LOW);
delayMicroseconds(2);
digitalWrite(triggerPins[i], HIGH);
delayMicroseconds(10);
digitalWrite(triggerPins[i], LOW);
durations[i] = pulseIn(echoPins[i], HIGH);
distances[i] = durations[i] * 0.034 / 2;
}
// Send distance data to Processing
for (int i = 0; i < numberOfSensors; i++) {
Serial.print(distances[i]);
if (i < numberOfSensors - 1) {
Serial.print(",");
}
}
Processing Code
Reading Arduino data and creating noises based on sensor-detected distances is the most crucial processing activity. The essential code that needs to execute in order to maintain the system's responsiveness and interactivity is serialEvent()
Key part of Processing Code: Handling Serial Data
In Processing, this part manages sensor distance data, toggles sound, and dynamically modifies pitch. It guarantees smooth communication, giving the system a lively and user-responsive feel.
void serialEvent(Serial myPort) {
String data = myPort.readStringUntil('\n'); // Read data from Arduino
if (data != null) {
data = trim(data); // Clean up the data
if (data.equals("Button Pressed")) {
soundOn = !soundOn;
redBackground = !redBackground;
} else if (data.startsWith("Counter: ")) {
int newCounter = int(data.substring(9)); // Get rotary encoder value
if (newCounter > counter) {
pitchOffset += 100;
} else {
pitchOffset -= 100;
}
pitchOffset = constrain(pitchOffset, -200, 600); // Constrain the pitch value
counter = newCounter;
} else {
String[] values = split(data, ',');
if (values.length == 5) {
for (int i = 0; i < 5; i++) {
distances[i] = int(values[i]);
}
}
}
}
}
The Sonic Pentagon was disassembled after the IMA Show presentation to guarantee that every part was put back together for usage in the future. Electronic parts, such as the Arduino Mega, breadboard and sensors, were put back into the Interaction Lab inventory. While the panels and other parts were put away for possible future use and rest for disposal. For upcoming students, this procedure guarantees accessibility and sustainability.
Full Code for Arduino:
#include <Adafruit_NeoPixel.h>
int numberOfSensors = 5;
int numberOfPixels = 60;
int triggerPins[] = { 3, 11, 9, 7, 5 };
int echoPins[] = { 2, 10, 8, 6, 4 };
long durations[5];
int distances[5];
int neoPixelPin = 16;
Adafruit_NeoPixel strip = Adafruit_NeoPixel(numberOfPixels, neoPixelPin, NEO_GRB + NEO_KHZ800);
int rotaryEncoderClockPin = 21;
int rotaryEncoderDataPin = 20;
int rotaryEncoderButtonPin = 19;
int lastStateClockPin = 0;
int currentStateClockPin = 0;
int encoderCount = 0;
void setup() {
Serial.begin(9600);
for (int i = 0; i < numberOfSensors; i++) {
pinMode(triggerPins[i], OUTPUT);
pinMode(echoPins[i], INPUT);
}
pinMode(rotaryEncoderClockPin, INPUT);
pinMode(rotaryEncoderDataPin, INPUT);
pinMode(rotaryEncoderButtonPin, INPUT);
strip.begin();
strip.show();
}
void loop() {
for (int i = 0; i < numberOfSensors; i++) {
digitalWrite(triggerPins[i], LOW);
delayMicroseconds(2);
digitalWrite(triggerPins[i], HIGH);
delayMicroseconds(10);
digitalWrite(triggerPins[i], LOW);
durations[i] = pulseIn(echoPins[i], HIGH);
distances[i] = durations[i] * 0.034 / 2;
}
for (int i = 0; i < numberOfSensors; i++) {
Serial.print(distances[i]);
if (i < numberOfSensors - 1) {
Serial.print(",");
}
}
Serial.println();
currentStateClockPin = digitalRead(rotaryEncoderClockPin);
if (currentStateClockPin != lastStateClockPin) {
if (digitalRead(rotaryEncoderDataPin) != currentStateClockPin) {
encoderCount++;
} else {
encoderCount--;
}
Serial.print("Encoder Count: ");
Serial.println(encoderCount);
}
if (digitalRead(rotaryEncoderButtonPin) == LOW) {
Serial.println("Button Pressed");
delay(300);
}
lastStateClockPin = currentStateClockPin;
if (Serial.available()) {
String inputData = Serial.readStringUntil('\n');
inputData.trim();
if (inputData.startsWith("L")) {
int sensorIndex = inputData.substring(1, 2).toInt();
updateNeoPixelLights(sensorIndex);
}
}
delay(50);
}
void updateNeoPixelLights(int sensorIndex) {
int red = 255, green = 0, blue = 0;
if (sensorIndex == 1) {
green = 255;
red = 0;
} else if (sensorIndex == 2) {
blue = 255;
red = 0;
green = 0;
} else if (sensorIndex == 3) {
red = 255;
green = 150;
blue = 0;
} else if (sensorIndex == 4) {
red = 255;
green = 0;
blue = 255;
}
for (int i = 0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, red, green, blue);
}
strip.show();
}
Full Code for Processing:
import processing.serial.*;
import processing.sound.*;
Serial myPort;
SqrOsc[] oscillators;
int[] distances = new int[5];
int threshold = 15;
int counter = 0;
boolean soundOn = true;
boolean redBackground = true;
float pitchOffset = 0;
float[] notes = {261.63, 329.63, 392.00, 523.25, 659.25};
void setup() {
size(800, 600);
oscillators = new SqrOsc[5];
for (int i = 0; i < 5; i++) {
oscillators[i] = new SqrOsc(this);
oscillators[i].play();
oscillators[i].amp(0);
}
myPort = new Serial(this, "COM10", 9600);
myPort.bufferUntil('\n');
}
void draw() {
if (redBackground) {
background(255, 0, 0);
} else {
background(0, 255, 0);
}
for (int i = 0; i < 5; i++) {
if (distances[i] > 0 && distances[i] <= threshold) {
float freq = notes[i] + pitchOffset;
float amp = map(distances[i], 2, threshold, 1.0, 0.5);
oscillators[i].freq(freq);
oscillators[i].amp(amp);
} else {
oscillators[i].amp(0);
}
}
}
void serialEvent(Serial myPort) {
String data = myPort.readStringUntil('\n');
if (data != null) {
data = trim(data);
if (data.equals("Button Pressed")) {
soundOn = !soundOn;
redBackground = !redBackground;
} else if (data.startsWith("Counter: ")) {
int newCounter = int(data.substring(9));
if (newCounter > counter) {
pitchOffset += 100;
} else {
pitchOffset -= 100;
}
pitchOffset = constrain(pitchOffset, -200, 600);
counter = newCounter;
} else {
String[] values = split(data, ',');
if (values.length == 5) {
for (int i = 0; i < 5; i++) {
distances[i] = int(values[i]);
}
}
}
}
}
Tj, Reac. YouTube, YouTube, www.youtube.com/watch?v=Mgy1S8qymx0.
Anon, et al. “Ultrasonic Sensor HC-SR04 and Arduino - Complete Guide.” How To Mechatronics, 18 Feb. 2022, howtomechatronics.com/tutorials/arduino/ultrasonic-sensor-hc-sr04/.
LME Editorial Staff. (2023, February 1). How Rotary Encoder Works and Interface It with Arduino. Last Minute Engineers. https://lastminuteengineers.com/rotary-encoder-arduino-tutorial/
SinOsc / Libraries. (n.d.). Processing. https://processing.org/reference/libraries/sound/SinOsc.html
Processing. (n.d.). processing-sound/src/processing/sound/Oscillator.java at main · processing/processing-sound. GitHub. https://github.com/processing/processing-sound/blob/main/src/processing/sound/Oscillator.java