xxxxxxxxxx
382
let video;
let audio;
let level;
let pixelArray; // Array to hold grayscale values
let j = 80; // Width of the pixel array
let k = 80; // Height of the pixel array
// Floating Text Variables
let textLines; // Table to hold text lines from CSV
let floatingTexts = []; // Active floating texts
let slots = []; // Array holding a single floatingText or null per slot
let currentLineIndex = 0; // To track current text line
let totalSlots = 30; // Number of vertical slots (rows)
let font; // Custom font
// Configuration for Text Concatenation
let TEXT_MULTIPLIER = 2; // Multiplier for window width to ensure text length
// Arrays to hold fixed directions and speeds per slot
let directions = [];
let speeds = [];
function preload() {
// Load the CSV file
// Ensure that your CSV file is in the same directory or provide the correct path
// The CSV should have one text line per row, with a header (e.g., "line")
textLines = loadTable('textLines.csv', 'csv', 'header'); // Adjust the path and options as needed
// Load a custom font for better text appearance (Uncomment if available)
// font = loadFont('Font.ttf');
}
function setup() {
createCanvas(windowWidth, windowHeight);
// frameRate(30); // Increased frame rate for smoother text movement
// Initialize vertical slots as null (no floating text initially)
for (let i = 0; i < totalSlots; i++) {
slots.push(null); // Each slot holds one floating text or null
// Assign a fixed direction randomly
let dir = random(['ltr','rtl']);
directions.push(dir);
// Assign a fixed speed per slot to prevent overlapping
let speed = random(3, 5); // Adjust the speed range as needed
speeds.push(speed);
}
// Create a video capture from the webcam
video = createCapture(VIDEO, { flipped: true });
video.size(j, k); // Set the size of the video
video.hide(); // Hide the default video element
// Create an audio input from the microphone
audio = new p5.AudioIn();
audio.start(); // Start microphone
}
function draw() {
background(17, 38, 56); // Set background color
// Update and display floating texts
updateFloatingTexts();
// Video Processing
videoToGray(); // Convert video to grayscale
// Create array of only the first pixels in the 4-element group from video
pixelArray = video.pixels.filter((_, index) => index % 4 === 0);
// Map the audio level to contrast factor (Inverse Mapping)
// Higher audio level -> Lower contrast factor
let levelFactor = map(audio.getLevel(), 0, 1, 1.8, 10);
// Apply high contrast transformation
pixelArray = applyHighContrast(pixelArray, levelFactor);
// Draw the alpha channel filter based on probabilities
drawAlphaFilter();
}
// Function to convert video to grayscale
function videoToGray() {
// Load the pixels from the video
video.loadPixels();
// Check if the video has pixels loaded
if (video.pixels.length > 0) {
// Convert to grayscale
for (let i = 0; i < video.pixels.length; i += 4) {
let r = video.pixels[i]; // Red
let g = video.pixels[i + 1]; // Green
let b = video.pixels[i + 2]; // Blue
// Calculate grayscale value
let gray = (r + g + b) / 3;
// Set the pixel color to the grayscale value
video.pixels[i] = gray; // Red
video.pixels[i + 1] = gray; // Green
video.pixels[i + 2] = gray; // Blue
// Alpha channel (video.pixels[i + 3]) remains unchanged
}
// Update the video pixels
video.updatePixels();
}
}
// Function to apply high contrast based on contrast factor
function applyHighContrast(array, contrastFactor) {
// Stretch the grayscale values to increase contrast
let minVal = Math.min(array);
let maxVal = Math.max(array);
// Prevent division by zero if all values are the same
if (minVal === maxVal) {
return array.map(() => 255);
}
// Apply contrast stretching with a scaling factor
return array.map(value => {
// Apply contrast stretching
let stretchedValue = ((value - minVal) * (255 / (maxVal - minVal))) * contrastFactor;
// Clip the value to ensure it stays within bounds
return constrain(Math.round(stretchedValue), 0, 255);
});
}
// Function to draw the alpha channel filter based on probabilities
function drawAlphaFilter() {
noStroke();
// Iterate through each cell in the grid
for (let y = 0; y < k; y++) {
for (let x = 0; x < j; x++) {
let index = x + y * j;
let grayValue = pixelArray[index];
// Calculate alpha value
// Ensure alphaValue is within 0-250 for better visibility
let alphaValue = constrain(grayValue, 0, 250);
// Set fill color to background color with the calculated alpha for overlay effect
fill(17, 38, 56, alphaValue);
// Calculate the position and size of each rectangle
let rectWidth = windowWidth / j;
let rectHeight = windowHeight / k;
let rectX = x * rectWidth;
let rectY = y * rectHeight;
rect(rectX, rectY, rectWidth, rectHeight);
}
}
}
// Function to update and manage floating texts
function updateFloatingTexts() {
// Update and display existing floating texts
for (let i = floatingTexts.length - 1; i >= 0; i--) {
let ft = floatingTexts[i];
ft.update();
ft.display();
// Remove if off-screen
if (ft.isOffScreen()) {
floatingTexts.splice(i, 1);
// Also remove from its slot
let s = ft.slot;
// slots[s] = null; // Mark the slot as free
}
}
// Iterate through each slot to manage floating texts
for (let s = 0; s < totalSlots; s++) {
if (slots[s] === null) {
// If the slot is free, add a new floating text
let newText = getNextText();
if (newText) {
let ft = new FloatingText(newText, s);
floatingTexts.push(ft);
slots[s] = ft; // Assign the floating text to the slot
}
} else {
// If the slot is occupied, check if the tail has entered the screen
let lastText = slots[s];
if (lastText.direction === 'ltr') { // Left-to-Right
// Check if the tail has entered the screen
if (lastText.x - lastText.getTextWidth(lastText.text) >= windowWidth * 0.1) {
// Safely remove the old floating text before adding a new one
// removeFloatingText(lastText);
// Add a new floating text to the slot
let newText = getNextText();
if (newText) {
let ft = new FloatingText(newText, s);
floatingTexts.push(ft);
slots[s] = ft; // Assign the new floating text to the slot
}
}
} else { // Right-to-Left
// Check if the tail has entered the screen (x - width <= windowWidth)
if (lastText.x + lastText.getTextWidth(lastText.text) <= windowWidth * 0.9) {
// Safely remove the old floating text before adding a new one
// removeFloatingText(lastText);
// Add a new floating text to the slot
let newText = getNextText();
if (newText) {
let ft = new FloatingText(newText, s);
floatingTexts.push(ft);
slots[s] = ft; // Assign the new floating text to the slot
}
}
}
}
}
}
// Function to remove a floating text from the global array and free its slot
function removeFloatingText(ft) {
let index = floatingTexts.indexOf(ft);
if (index !== -1) {
floatingTexts.splice(index, 1);
}
// Free the slot
slots[ft.slot] = null;
}
// Function to retrieve and concatenate text lines until desired length is achieved
function getNextText() {
// Reset index if end is reached
if (currentLineIndex >= textLines.getRowCount()) {
currentLineIndex = 0; // Reset to start
}
let combinedText = '';
let estimatedWidth = 0;
let tempIndex = currentLineIndex;
let concatenationAttempts = 0;
let maxAttempts = textLines.getRowCount(); // Prevent infinite loops
// Loop to concatenate lines until the combined text is sufficiently long
while (estimatedWidth < windowWidth * TEXT_MULTIPLIER && concatenationAttempts < maxAttempts) {
let textLine = textLines.getString(tempIndex, 0);
if (!textLine) break; // If no more lines available
combinedText += (combinedText.length > 0 ? ' ' : '') + textLine;
tempIndex++;
// Reset if at the end of the table
if (tempIndex >= textLines.getRowCount()) {
tempIndex = 0;
}
// Estimate text width using p5.js's textWidth
textSize(24); // Set a default size for estimation
estimatedWidth = textWidth(combinedText);
concatenationAttempts++;
// Break if the same index is on loop to prevent infinite concatenation
if (tempIndex === currentLineIndex) break;
}
// Update the currentLineIndex to tempIndex
currentLineIndex = tempIndex;
return combinedText;
}
// FloatingText Class Definition
class FloatingText {
constructor(txt, slot) {
this.text = txt;
this.slot = slot;
// Assign direction and speed based on slot's fixed direction and speed
this.direction = directions[this.slot]; // 'ltr' or 'rtl'
this.speed = speeds[this.slot]; // Fixed speed per slot
// Starting position based on direction
if (this.direction === 'ltr') {
this.x = 0;
} else {
this.x = windowWidth;
}
// Y position based on slot
let padding = 5; // Padding from top and bottom
this.y = map(this.slot, 0, totalSlots - 1, padding, windowHeight - padding);
// Text properties
this.size = random(20, 24); // Random text size for variability
this.alpha = 255; // Full opacity
// Assign a consistent color per floating text
this.color = color(255, 255, 255, this.alpha); // White with full opacity
}
getTextWidth(txt) {
textSize(this.size);
return textWidth(txt);
}
update() {
// Move based on direction
if (this.direction === 'ltr') {
this.x += this.speed;
} else {
this.x -= this.speed;
}
// Fade out when nearing the end
if (this.direction === 'ltr') {
if (this.x - this.getTextWidth(this.text)> windowWidth * 0.8) { // Threshold to start fading
this.alpha = map(this.x - this.getTextWidth(this.text), windowWidth * 0.8, windowWidth, 255, 0);
this.color.setAlpha(this.alpha);
}
} else {
if (this.x + this.getTextWidth(this.text) < windowWidth * 0.2) { // Threshold to start fading
this.alpha = map(this.x + this.getTextWidth(this.text), windowWidth * 0.2, -this.getTextWidth(this.text), 255, 0);
this.color.setAlpha(this.alpha);
}
}
}
display() {
noStroke();
fill(this.color);
textSize(this.size);
if (this.direction === 'ltr') {
textAlign(RIGHT, CENTER);
} else {
textAlign(LEFT, CENTER);
}
// Use a custom font if loaded
// textFont(font);
text(this.text, this.x, this.y);
}
isOffScreen() {
// Check if the text has moved completely off the canvas
if (this.direction === 'ltr') {
return this.x - this.getTextWidth(this.text) > windowWidth;
} else {
return this.x + this.getTextWidth(this.text) < 0;
}
}
}
// Adjust canvas size and update floating texts on window resize
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
// Update Y positions of floating texts based on new window size
for (let ft of floatingTexts) {
let padding = 5; // Padding from top and bottom
ft.y = map(ft.slot, 0, totalSlots - 1, padding, windowHeight - padding);
}
}
// The rest two functions comes from https://editor.p5js.org/mangtronix/full/t4G0erH1B
function keyTyped() {
// $$$ For some reason on Chrome/Mac you may have to press f twice to toggle. Works correctly on Firefox/Mac
if (key === 'f') {
toggleFullscreen();
}
// uncomment to prevent any default behavior
// return false;
}
// Toggle fullscreen state. Must be called in response
// to a user event (i.e. keyboard, mouse click)
function toggleFullscreen() {
let fs = fullscreen(); // Get the current state
fullscreen(!fs); // Flip it!
}