Mac Pro M1 — layers not saving + Arduino OSC connection?!

54 views
Skip to first unread message

Tarvo Metspalu

unread,
Jan 13, 2024, 11:05:23 AM1/13/24
to VPT forum
Hello!

I can see that VPT8 is a powerful tool. I tried to use it with my art college students with an interactive art project, but even with my Arduino experience and physics + hardware background, I seem to fail connecting the Arduino's serial communications with VPT8. I roamed around the documentation and the forums + ping-ponged many ideas with ChatGPT, but I am on the verge of giving up with VPT8 and starting from scratch with some other solution.

Namely, my idea is as simple as it gets and it's nothing original: there are 6 pushbuttons. With each button, you should be able to initiate a video somewhere on a big screen. That's it. Here's my hardware setup:
20240105_162559.jpg

I tried to do it through fading layers following the provided example. 
From this:

to this (just the output provided, where each keypress sends a block of commands):
Screenshot 2024-01-08 at 10.59.11.png

No connection made, although I selected the correct COM port + baud rate on Serial tab and clicked "Listen". Thereafter I was confused, whether anything else is necessary. One thing I realised while I am writing this, is that I did not do anything in the "cluelist" tab (moreover, as I cannot recall this being mentioned in the 1hr long tutorial video, i.e. a connection between OSC and cuelist — it's my first time with OSC), but then again, when I change the commands in Arduino, VPT should follow suit despite what I've written, correct? As long as the commands are according to the documentation. In other words, I shouldn't double anything in VPT or should I? Should I make some additional steps, which I am constantly missing? I even closed the Arduino IDE as some suggested, but to no avail.

Here's my current code, where I tried to switch from switching layers to switching presets (the "/fullscreen 1" was just to test whether anything happens at all):

const int numWindows = 6; // Number of windows
const int duration = 300; // Duration in milliseconds (e.g., 5 seconds) when the user cannot press a button
const int standby = 60000; // Duration in ms when the user has left the buttons untouched (e.g., 60 seconds or 1 minute)

// Button and LED pin arrays
int pushButtonPins[numWindows] = {A2, A3, A4, A5, A0, A1}; // Analog pins (A2-A5) and digital pins (A0, A1)
int ledPins[numWindows] = {2, 3, 4, 5, 6, 8}; // Digital pins (D2-D6 & D8, because D7 had some issues)

bool ledStates[numWindows];

unsigned long startTimes[numWindows]; // Array to store the start times for each window
boolean isRunning[numWindows]; // Array to track whether the LED is currently on for each window

boolean anyLedRunning = false; // Flag to indicate whether any LED is currently active

#include <ArduinoOSC.h> // Include the ArduinoOSC library

// Define the OSC message format
const char* oscMessageFormat = "O %s %s"; // "O <address> <value>"
// OSC addresses for different scenes
const char* scenes[numWindows] = {"/fullscreen 1", "/preset 2", "/preset 3", "/preset 4", "/preset 5", "/preset 6"};
const char* emptyScene = "/preset 9"; // Assuming this is an empty scene

unsigned long lastButtonPressTime; // Variable to store the time of the last button press


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

for (int i = 0; i < numWindows; i++) {
pinMode(pushButtonPins[i], INPUT_PULLUP);
pinMode(ledPins[i], OUTPUT);
digitalWrite(ledPins[i], HIGH); // Set LED HIGH
delay(200); // Adjust delay as needed for the initialization
digitalWrite(ledPins[i], LOW); // Set LED LOW
}

for (int i = 0; i < numWindows; i++) {
startTimes[i] = millis(); // Initialize start times to current millis value
}

// Get stuff ready for VPT
Serial.flush();
delay(1000);
Serial.println(-1, DEC); //send -1 to let VPT know the serial port is ready
}

// Helpful additional functions:
void sendOSC(const char* address, const char* value) {
char oscMessage[100];
sprintf(oscMessage, oscMessageFormat, address, value);
Serial.println(oscMessage);
}

void activateScene(int sceneNumber) {
// Activate the specified scene
for (int i = 0; i < numWindows; i++) {
if (i + 1 == sceneNumber) {
digitalWrite(ledPins[i], HIGH); // Turn on LED for the active scene
sendOSC(scenes[i], ""); // Send empty string to activate the corresponding preset
} else {
digitalWrite(ledPins[i], LOW); // Turn off LEDs for inactive scenes
}
}
}


void loop() {
boolean anyButtonPressed = false;

// Check each button
for (int i = 0; i < numWindows; i++) {
if (digitalRead(pushButtonPins[i]) == LOW) {
anyButtonPressed = true;
// Button pressed, check if dead time has passed
if (millis() - lastButtonPressTime >= duration) {
// Dead time has passed, activate the corresponding scene
activateScene(i + 1); // Scene numbers start from 1
startTimes[i] = millis(); // Reset the timer for the pressed button
isRunning[i] = true; // Set the LED state to HIGH
anyLedRunning = true; // Set the global flag
lastButtonPressTime = millis(); // Update the last button press time
}
}
}

// Check if all LEDs have been running for the standby duration
if (!anyButtonPressed && anyLedRunning && millis() - startTimes[0] >= standby) {
for (int i = 0; i < numWindows; i++) {
digitalWrite(ledPins[i], LOW);
isRunning[i] = false;
anyLedRunning = false;
}
}
}


(Ah, right.. And each keypress also turns on a specific LED to notify which video is playing. And when 60 seconds have passed, it should go to a blackout screen.)

But as I soon found out, my M1 Macbook Pro VPT version doesn't seem to save layers, when I close the project. Not to mention reacting to anything I send over Arduino... So..
Although I'd like to make use of the capabilities of VPT8, I am losing my mind over the simplest of matters... I even considered making Arduino a MIDI device as this seems to work better.. Theoretically.. because the version of Arduino I have does not support MIDI over USB. Nevertheless, Serial monitor operates perfectly, so I should still be able to communicate over serial.

Please help.. Or I will have to ditch VPT and never suggest it to my students as I cannot seem to connect Arduinos to VPT. :( 



Tarvo Metspalu

unread,
Jan 14, 2024, 5:07:17 AM1/14/24
to VPT forum
I went for the Arduino + Processing solution. Works like a charm and gives me all of the controls I need.
If anybody else is in a similar pickle, here's a demo for Arduino and Processing with 6 buttons and 6 LEDs.
Arduino-setup.jpg
PS. This hideous Arduino Nano was NOT soldered by me — it was the work of a beginner new to soldering. But it works! :D 

ARDUINO code:
const int numWindows = 6;
const int duration = 300;
const int standby = 60000; // Not implemented in the final version, although could be used to initiate idle behaviour, i.e. selecting a random video

int pushButtonPins[numWindows] = {A2, A3, A4, A5, A0, A1};
int ledPins[numWindows] = {2, 3, 4, 5, 6, 8};

unsigned long startTimes[numWindows];
boolean isRunning[numWindows];

boolean anyLedRunning = false;
unsigned long lastButtonPressTime;

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

for (int i = 0; i < numWindows; i++) {
pinMode(pushButtonPins[i], INPUT_PULLUP);
pinMode(ledPins[i], OUTPUT);
digitalWrite(ledPins[i], HIGH);
delay(300);
digitalWrite(ledPins[i], LOW);
}

for (int i = 0; i < numWindows; i++) {
startTimes[i] = millis();
}
}

void activateScene(int sceneNumber) {
for (int i = 0; i < numWindows; i++) {
if (i + 1 == sceneNumber) {
digitalWrite(ledPins[i], HIGH);
} else {
digitalWrite(ledPins[i], LOW);
}
}
}

void loop() {
boolean anyButtonPressed = false;

while (Serial.available() > 0) {
// Read the message from Processing
String message = Serial.readStringUntil('\n');
message.trim();

// Check if the message is LED_OFF
if (message.equals("LED_OFF")) {
// Turn off all LEDs
for (int i = 0; i < numWindows; i++) {
digitalWrite(ledPins[i], LOW);
isRunning[i] = false;
anyLedRunning = false;
}
}
}

for (int i = 0; i < numWindows; i++) {
if (digitalRead(pushButtonPins[i]) == LOW) {
anyButtonPressed = true;

if (millis() - lastButtonPressTime >= duration) {
activateScene(i + 1);
startTimes[i] = millis();
isRunning[i] = true;
anyLedRunning = true;
lastButtonPressTime = millis();

// Send button index to Processing
Serial.print(i);
Serial.print("\n");
}
}
}

if (!anyButtonPressed && anyLedRunning && millis() - startTimes[0] >= standby) {
for (int i = 0; i < numWindows; i++) {
digitalWrite(ledPins[i], LOW);
isRunning[i] = false;
anyLedRunning = false;
}
}
}


PROCESSING code:
(NB! Make sure to put the video files in the folder called "data" to the folder, where you save your Processing sketch!)
import processing.serial.*;
import processing.video.*;

Serial port;
String[] videos = {
"video1.mp4",
"video2.mp4",
"video3.mp4",
"video4.mp4",
"video5.mp4",
"video6.mp4"
};
Movie[] videoPlayers = new Movie[6];

// Coordinates of the videos:
PVector[] videoCoordinates;

int currentVideoIndex = 0;
boolean videoPlaying = false;
float fadeInDuration = 1; // Duration for fade-in in seconds
float fadeOutDuration = 1; // Duration for fade-out in seconds
float fadeStartTime = 0;

void setup() {
size(1920, 1080);
// Set the sketch to full-screen
fullScreen();
// Initialize video coordinates based on layout parameters
videoCoordinates = new PVector[]{
new PVector(0.0 * width, 0.0 * height), // Video 1 coordinates (top-left)
new PVector(0.6 * width, 0.0 * height), // Video 2 coordinates (top-right)
new PVector(0.0 * width, 0.4 * height), // Video 3 coordinates (center-left)
new PVector(0.6 * width, 0.4 * height), // Video 4 coordinates (bcenter-right)
new PVector(0.0 * width, 0.8 * height), // Video 5 coordinates (bottom-left)
new PVector(0.6 * width, 0.8 * height) // Video 6 coordinates (bottom-right)
};
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
printArray(Serial.list());
String portName = Serial.list()[7]; // Change to the appropriate Arduino port name
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
port = new Serial(this, portName, 9600);
// Load all videos but start playing only the first one
for (int i = 0; i < videos.length; i++) {
videoPlayers[i] = new Movie(this, videos[i]);
videoPlayers[i].noLoop(); // Do not loop initially
}
}

void draw() {
background(0); // Fill the background with black

// Draw the current video at its specified coordinates
if (videoPlaying && videoPlayers[currentVideoIndex] != null) {
PVector coords = videoCoordinates[currentVideoIndex];
float videoWidth = width / 2;
float videoHeight = height / 3;

// Check if fade-in is in progress
if (fadeStartTime > 0) {
float fadeProgress = (millis() - fadeStartTime) / (fadeInDuration * 1000);
tint(255, map(fadeProgress, 0, 1, 0, 255));
}

image(videoPlayers[currentVideoIndex], coords.x, coords.y, videoWidth, videoHeight);

// Check if the video has played once
if (videoPlayers[currentVideoIndex].time() >= videoPlayers[currentVideoIndex].duration()) {
// Start the fade-out
if (fadeStartTime == 0) {
fadeStartTime = millis(); // Record the start time of the fade
}

// Calculate fade progress
float fadeProgress = (millis() - fadeStartTime) / (fadeOutDuration * 1000);

// Check if fade-out is complete
if (fadeProgress >= 1.0) {
videoPlayers[currentVideoIndex].pause(); // Pause the current video
videoPlayers[currentVideoIndex].jump(0); // Jump to the beginning
fadeStartTime = 0; // Reset fade start time
videoPlaying = false;

// Send a message to Arduino to switch off the corresponding LED
port.write("LED_OFF\n");
}
}
}
}

void serialEvent(Serial p) {
String message = p.readStringUntil('\n');
if (message != null) {
message = message.trim();
int buttonIndex = int(message);
if (buttonIndex >= 0 && buttonIndex < videos.length) {
switchVideo(buttonIndex);
}
}
}

void switchVideo(int index) {
if (index >= 0 && index < videos.length) {
println("Switching to Video: " + videos[index]);

// Pause all videos
for (Movie player : videoPlayers) {
if (player != null) {
player.pause();
}
}

// If the same button is pressed again, start the video from the beginning
if (index == currentVideoIndex) {
videoPlayers[currentVideoIndex].jump(0); // Jump to the beginning
} else {
currentVideoIndex = index;
}

videoPlayers[currentVideoIndex].play(); // Start playing

// Start the fade-in
fadeStartTime = millis();
videoPlaying = true;
}
}


void movieEvent(Movie m) {
m.read(); // Read the next available frame
}
Reply all
Reply to author
Forward
0 new messages