/** * ParVaguesViz - Cyberpunk TidalCycles Visualizer * * A Processing-based visualizer that works with SuperDirt OSC messages * to create cyberpunk-style visualizations for TidalCycles performances. * * Features: * - Automatic track detection and visualization * - Cyberpunk aesthetic with neon colors and grid effects * - Real-time audio-reactive visual elements * - Orbit-based color schemes * - Metadata system for track identification */ import oscP5.*; import netP5.*; // Processing 4.x compatibility check boolean isProcessing4 = true; // OSC connection settings OscP5 oscP5; NetAddress superdirtAddress; int listenPort = 57120; // Default SuperDirt port // Visualization components TrackManager trackManager; Grid grid; Background background; ParticleSystem particleSystem; GlitchEffect glitchEffect; // Metadata system MetadataSystem metadataSystem; // Sample analyzer SampleAnalyzer sampleAnalyzer; // UI settings boolean debug = false; boolean showHelp = false; boolean showMetadata = false; PFont debugFont; PFont titleFont; PFont metadataFont; // Timing float bpm = 120; float cps = 0.5; float currentCycle = 0; float elapsedTime = 0; float lastBeatTime = 0; // Colors (cyberpunk palette) color[] orbitColors = { #00FFFF, // Cyan (d1 - kick) #FF00FF, // Magenta (d2 - snare) #00FF99, // Neon green (d3 - drums) #FF5500, // Orange (d4 - bass) #9900FF, // Purple (d5) #FFFF00, // Yellow (d6) #FF0066, // Pink (d7) #0099FF, // Blue (d8 - breaks) #33FF33, // Green (d9) #FF3300, // Red (d10) #CC00FF, // Violet (d11) #00CCFF, // Light blue (d12) #FFFFFF, // White (d13-d16) #FFFFFF, #FFFFFF, #FFFFFF }; void setup() { // Apply Processing 4.x compatibility fixes checkProcessingVersion(); applyProcessing4Fixes(); size(1280, 720, P3D); frameRate(60); smooth(8); // Initialize OSC oscP5 = new OscP5(this, listenPort); superdirtAddress = new NetAddress("127.0.0.1", listenPort); // Initialize components trackManager = new TrackManager(); grid = new Grid(); background = new Background(); particleSystem = new ParticleSystem(); glitchEffect = new GlitchEffect(); // Initialize metadata system metadataSystem = new MetadataSystem(); // Initialize sample analyzer sampleAnalyzer = new SampleAnalyzer(); // Load fonts debugFont = createFont("Courier New Bold", 12); titleFont = createFont("Arial Bold", 24); metadataFont = createFont("Arial", 14); // Print startup message println("ParVaguesViz started"); println("Listening for SuperDirt OSC messages on port " + listenPort); } void draw() { // Update timing elapsedTime = millis() / 1000.0; // Clear background with fade effect background.update(); background.display(); // Update grid grid.update(); grid.display(); // Update and display tracks trackManager.update(); trackManager.display(); // Update and display particles particleSystem.update(); particleSystem.display(); // Apply glitch effects glitchEffect.apply(); // Draw UI elements if debug mode is on if (debug) { drawDebugInfo(); } // Draw metadata overlay if enabled if (showMetadata) { drawMetadataOverlay(); } // Draw help if enabled if (showHelp) { drawHelp(); } } // Handle OSC messages void oscEvent(OscMessage msg) { // Check for metadata messages if (msg.addrPattern().equals("/parvagues/metadata")) { metadataSystem.processMetadataMessage(msg); return; } // Forward the message to our handler if (msg.addrPattern().equals("/dirt/play")) { handleDirtMessage(msg); } else if (msg.addrPattern().equals("/cps")) { updateCPS(msg); } } // Process SuperDirt message void handleDirtMessage(OscMessage msg) { // Extract basic information int orbit = -1; String sound = ""; float cycle = 0; float delta = 0; float gain = 1.0; float pan = 0.5; // Extract all parameters from the message for (int i = 0; i < msg.typetag().length(); i++) { String paramName = msg.get(i).stringValue(); if (paramName.equals("orbit")) { orbit = msg.get(i+1).intValue(); } else if (paramName.equals("s")) { sound = msg.get(i+1).stringValue(); } else if (paramName.equals("cycle")) { cycle = msg.get(i+1).floatValue(); } else if (paramName.equals("delta")) { delta = msg.get(i+1).floatValue(); } else if (paramName.equals("gain")) { gain = msg.get(i+1).floatValue(); } else if (paramName.equals("pan")) { pan = msg.get(i+1).floatValue(); } } // Only process valid messages with an orbit if (orbit >= 0) { // Update metadata system with sample information metadataSystem.updateFromSample(orbit, sound); // Update sample analyzer sampleAnalyzer.processSample(orbit, sound, gain, delta); // Beat detection logic currentCycle = cycle; float now = millis() / 1000.0; if (now - lastBeatTime > 0.1) { // Debounce lastBeatTime = now; grid.trigger(0.3); // Trigger grid effect on beats glitchEffect.trigger(0.1); // Small glitch on beats } // Create a new visual event for this sound trackManager.addEvent(orbit, sound, gain, pan, delta); // Add particles particleSystem.addParticles(orbit, pan, gain); } } // Update timing information from CPS messages void updateCPS(OscMessage msg) { if (msg.checkTypetag("f")) { cps = msg.get(0).floatValue(); bpm = cps * 60 * 4; // Convert to BPM // Update components with new timing grid.setCPS(cps); background.setCPS(cps); } } // Handle keyboard inputs void keyPressed() { if (key == 'd' || key == 'D') { debug = !debug; } else if (key == 'h' || key == 'H') { showHelp = !showHelp; } else if (key == 'g' || key == 'G') { grid.toggleStyle(); } else if (key == 'f' || key == 'F') { // Use the compatible fullscreen toggle handleFullscreenToggle(); } else if (key == 'r' || key == 'R') { // Reset all visuals trackManager.reset(); particleSystem.reset(); } else if (key == 'm' || key == 'M') { // Toggle metadata display showMetadata = !showMetadata; } } // Debug information display void drawDebugInfo() { fill(255); textFont(debugFont); textAlign(LEFT); text("FPS: " + int(frameRate), 10, 20); text("CPS: " + nf(cps, 0, 2) + " (BPM: " + int(bpm) + ")", 10, 35); text("Cycle: " + nf(currentCycle, 0, 2), 10, 50); text("Active Tracks: " + trackManager.getActiveTrackCount(), 10, 65); text("Particles: " + particleSystem.getParticleCount(), 10, 80); text("Tracked Samples: " + sampleAnalyzer.getSampleCount(), 10, 95); // Add hint about metadata fill(200); text("Press 'M' to toggle metadata display", 10, 125); } // Draw metadata overlay void drawMetadataOverlay() { // Semi-transparent background fill(0, 200); noStroke(); rect(width - 320, 10, 310, height - 20, 10); // Title fill(255); textFont(titleFont); textAlign(CENTER); text("Track Metadata", width - 165, 40); // Track information textFont(metadataFont); textAlign(LEFT); float y = 70; // Draw metadata for each active track ArrayList<Integer> activeOrbits = trackManager.getActiveOrbits(); if (activeOrbits.size() == 0) { text("No active tracks", width - 300, y); } else { for (Integer orbit : activeOrbits) { TrackMetadata metadata = metadataSystem.getMetadata(orbit); // Draw track name and type with track color fill(metadata.getTrackColor()); text("d" + (orbit + 1) + ": " + metadata.name, width - 300, y); text("Type: " + metadata.type, width - 300, y + 20); // Draw most recent sample fill(200); if (metadata.recentSamples.size() > 0) { text("Sample: " + metadata.recentSamples.get(metadata.recentSamples.size() - 1), width - 300, y + 40); } // Add analyzed features if available SampleFeatures features = sampleAnalyzer.getFeaturesForOrbit(orbit); if (features != null) { fill(180); text("Tempo: " + nf(features.tempo, 0, 1) + " BPM", width - 300, y + 60); text("Energy: " + nf(features.energy, 0, 2), width - 150, y + 60); } // Draw separator stroke(100); line(width - 300, y + 75, width - 30, y + 75); noStroke(); // Move to next track position y += 90; // Avoid drawing outside screen if (y > height - 50) break; } } } // Help information display void drawHelp() { fill(0, 180); noStroke(); rect(width/2 - 200, height/2 - 150, 400, 300); fill(255); textFont(titleFont); textAlign(CENTER); text("ParVaguesViz Controls", width/2, height/2 - 120); textFont(debugFont); textAlign(LEFT); String[] helpText = { "D - Toggle debug info", "H - Toggle help", "G - Change grid style", "F - Toggle fullscreen", "R - Reset visuals", "M - Toggle metadata display", "", "Automatically visualizes tracks d1-d16", "Special visualization for d8 breakbeats", "No TidalCycles configuration needed" }; for (int i = 0; i < helpText.length; i++) { text(helpText[i], width/2 - 180, height/2 - 80 + i * 20); } } // Check if currently fullscreen boolean sketchFullScreen() { if (isProcessing4) { return width == displayWidth && height == displayHeight; } else { return width == displayWidth && height == displayHeight; } }