fullscreen /*
* Blitz visualization - Creates circles in a wave pattern, with color determined by the right channel, and size with the left
*/
class Blitz extends Visualization
{
//The increment of the visualization, the smaller this number is, the more circles will appear
protected final int STEP = 24;
//Protected properties for location, max size and position
protected int _width, _height, _size, _x, _y;
//Array of color choices
protected color[] _colors;
//Constructor
public Blitz(int x, int y, int w, int h, int size, color[] c)
{
//Assign properties
_width = w;
_height = h;
_size = size;
_x = x;
_y = y;
_colors = c;
}
//Draw the visualization
public void draw(float[] left, float[] right, float[] mix, float left_level, float right_level, float mix_level)
{
//Enable smoothing
smooth();
//Loop through data
for(int i = 0; i < mix.length ; i += STEP)
{
//Determine x position based on current point in loop
int x = (int)(_x + ((float)(i-mix.length / 2) / mix.length) * _width);
//Determine y position based on level
int y = (int)(_y + (mix[i] * (_height / 2)));
//Set the ellipse mode
ellipseMode(CENTER);
//Determine the size of the circle
float size = 1 + ((_size - 1) * abs(left[i]));
//Get the color index
int index = round(abs(right[i]) * _colors.length);
//Ensure the index is not out of bounds
index = index == _colors.length ? 0 : index;
//Get the color to be used
color c = _colors[index];
//Remove stroke, set color, and draw the circle
noStroke();
fill(c);
ellipse(x, y, size, size);
}
//Disable smoothing
noSmooth();
}
}
/*
* Burst visualization - Creates rectangles within a circle pattern, length is determined by the mix channel, width by the right channel, and color by the left channel
*/
class Burst extends Visualization
{
//The increment of the visualization, the smaller this number is, the more rectangles will appear
protected final int STEP = 1;
//Maximum width of a rectangle
protected final int MAX_THICKNESS = 5;
//Properties for size and position
protected float _diameter, _x, _y;
//Array to store possible colors
protected color[] _colors;
//Constructor
Burst(float x, float y, float d, color[] c)
{
//Assign properties
_x = x;
_y = y;
_diameter = d;
_colors = c;
}
//Draw method
public void draw(float[] left, float[] right, float[] mix, float left_level, float right_level, float mix_level)
{
//Remove stroke
noStroke();
//Enable smoothing
smooth();
//Rect mode
rectMode(CENTER);
//Store original translation
pushMatrix();
translate(_x, _y); //Set center as 0,0
//Loop through channel data
for(int i = 0; i < mix.length; i += STEP)
{
//Set color based on volume of left channel
int colorIndex = (int)round(_colors.length * abs(left[i]));
if(colorIndex == _colors.length) colorIndex = 0;
fill(_colors[colorIndex]);
//Determing y position and length
float y = _diameter / 4 + (_diameter / 4 * abs(mix[i]) * 0.5) * (mix[i] < 0 ? -1 : 1);
float height = 1 + (_diameter / 4) * abs(mix[i]);
//Determine thickness
float thick = STEP + ((MAX_THICKNESS-STEP) * right[i]);
//Draw rect
rect(0, y, thick, height);
//Rotate to next rect
rotate(TWO_PI * ((float)i / mix.length));
}
//Disable smoothing
noSmooth();
//Restore original translation
popMatrix();
}
}
/*
* Chaos Visualization - Uses the sierpinski triangle fractal algorithm alongside a linked list to generate a visualization
*/
class Chaos extends Visualization
{
//Maximum iterations the algorithm can run
protected final int MAX_ITERATIONS = 4;
//Properties for size, position and color
protected int[][] _colors;
protected int _x, _y, _width, _height;
//Used to track the color to use
protected int _count = 0;
//Reference to the top of the stack
protected ChaosPulse _root;
//Constructor
public Chaos(int x, int y, int w, int h, int[][] c)
{
//Set properties
_x = x;
_y = y;
_width = w;
_height = h;
_colors = c;
}
//Draw method
public void draw(float[] left, float[] right, float[] mix, float left_level, float right_level, float mix_level)
{
//Get the amount of interations based on the mix level
int iterations = (int)(2 + mix_level * MAX_ITERATIONS);
//Ensure the interations does not go over the max
if(iterations > MAX_ITERATIONS) iterations = MAX_ITERATIONS;
//Determine the size based on the left channel
int w = (int)(_width * (left_level + 0.5));
int h = (int)(_height * (left_level + 0.5));
//Determine the rotation based on the right level
float a = (TWO_PI * (right_level * 2.5));
//Ensure the size does not extend past the total size of the visualization
w = w > _width ? _width : w;
h = h > _height ? _height : h;
//Increment count and ensure it does not go out of bounds
_count = (_count + 1) % _colors.length;
//Create a new stack element
ChaosPulse t = new ChaosPulse(_x, _y, w, h, _colors[_count], iterations, a);
//Set the parent of the new element to the current root
t.parent = _root;
//Re-assign the root
_root = t;
//Draw the root, and all other elements in the stack
_root.draw();
}
//Reset method
public void reset()
{
//Reset the stack to one element
_root = new ChaosPulse(_x, _y, _width / 2, _height / 2, _colors[0], 2, 0);
}
}
/*
* ChaosPulse - Used as a list element for the Chaos visualization
*/
class ChaosPulse
{
//Constants for animation and drawing
protected final int ALPHA_STEP = 25;
protected final int TOP_ALPHA = 255;
protected final int STROKE_WEIGHT = 2;
protected final boolean GLOBAL_ROTATE = false; //Determines if the entire sierpinski triangle is rotated, or the lowest level triangles
//Variables to determine the amount of width/height to increase by per frame
protected float _w_step, _h_step;
//Properties for color, size, alpha, iterations and rotation
protected int[] _c;
protected int _x, _y, _width, _height, _final_h, _final_w, _iter, _alpha = TOP_ALPHA;
protected float _angle;
//Parent element in the stack
public ChaosPulse parent;
//Constructor
public ChaosPulse(int x, int y, int w, int h, int[] c, int i, float a)
{
//Assign properties
_x = x;
_y = y;
_width = w / 4;
_height = h / 4;
_final_h = h;
_final_w = w;
_angle = a;
_c = c;
_iter = i;
//Determine the size step values
_w_step = (_final_w - _width) / ceil(TOP_ALPHA / ALPHA_STEP);
_h_step = (_final_h - _height) / ceil(TOP_ALPHA / ALPHA_STEP);
}
//Method to return the alpha
public int alpha()
{
return _alpha;
}
//Draw method
public void draw()
{
//If the item has a parent in the stack, draw that first
if(parent != null)
{
parent.draw();
//If the parent is now invisible remove from stack to free memory
if(parent.alpha() <= 0)
parent = null;
}
//Set the stroke color and weight, and remove fill
stroke(_c[0], _c[1], _c[2], _alpha);
noFill();
strokeWeight(STROKE_WEIGHT);
//Store original transformation
pushMatrix();
//Set the position as 0,0
translate(_x, _y);
//Rotate now if the entire triangle is to be rotated
if(GLOBAL_ROTATE) rotate(_angle);
//Call the recursive fractal method
drawTri(0, 0, _width, _height, _iter);
//Restore original transformation
popMatrix();
//Increase alpha and size
_width += _w_step;
_height += _h_step;
_alpha -= ALPHA_STEP;
}
//Recursive sierpinski triangle fractal method
protected void drawTri(int x, int y, int width, int height, int iter)
{
//If at the final stage of iteration
if(iter == 0) {
//Store the transformation
pushMatrix();
//Center of the triangle is 0,0
translate(x, y);
//Rotate now if the lowest level triangles are to be rotated
if(!GLOBAL_ROTATE) rotate(_angle);
//Draw a triangle
triangle(0, -height / 2, width / 2, height / 2, -width / 2, height / 2);
//Restore the transformation
popMatrix();
//Exit out of recursion
return;
}
//If recursion are still left, draw 3 inner triangles and supply the iteration minus 1
drawTri(x, y - height / 4 , width / 2, height / 2, iter - 1);
drawTri(x + height / 4, y + height / 4, width / 2, height / 2, iter - 1);
drawTri(x - height / 4, y+ height / 4, width / 2, height / 2, iter - 1);
}
}
/*
* CircleWave class to draw a wave form in a circle instead of a horizontal line (Wave class does this)
*/
class CircleWave
{
//The increment of the visualization, the smaller this number is, the more detailed the wave will be
protected int _step = 2;
//Thickness of the line depicting the wave
protected final int STROKE_WEIGHT = 2;
//Properties for size, color, and position
protected float _multiplier = 50; //The amount the wave will traverse in/out of the circumference
protected float _x = 0;
protected float _y = 0;
protected float _height = 0, _width = 0;
//Color of the wave
protected color _foreground = color(255);
//Constructor
public CircleWave(float x, float y, float w, float h, float multiplier, color c)
{
//Set properties
_x = x;
_y = y;
_width = w;
_height = h;
_multiplier = multiplier;
_foreground = c;
}
//Draw method
public void draw(float[] wave)
{
//Remove fill, set stroke weight and color and enable smoothing
noFill();
stroke(_foreground);
strokeWeight(STROKE_WEIGHT);
smooth();
//Start shape drawing
beginShape();
//Loop through data
for(int i = 0; i < wave.length; i += _step)
{
//Current angle
float angle = angle(i, wave.length);
//Distance from center
float distance_x = distance(wave[i], false);
float distance_y = distance(wave[i], true);
//Starting point
PVector start_point = point(wave[i], angle, distance_x, distance_y);
//Get ending point
int index = (i + _step);
if(index > wave.length - 1) index = 0;
float end_angle = angle(index, wave.length);
float end_distance_x = distance(wave[index], false);
float end_distance_y = distance(wave[index], true);
PVector end_point = point(wave[i], end_angle, end_distance_x, end_distance_y);
//Assign the vertex of the current line
vertex(start_point.x, start_point.y);
vertex(end_point.x, end_point.y);
}
//End the shape
endShape();
//Disable smoothing
noSmooth();
}
//Method to set the position
public void setPos(float x, float y)
{
_x = x;
_y = y;
}
//Method to determine the angle of the current wave peak
protected float angle(int index, int total)
{
return TWO_PI * ((float)index/(float)total);
}
//Method to determine the position of the current wave
protected float distance(float value, boolean y)
{
return (y ? _height / 2 : _width / 2) + _multiplier * value;
}
//Get the x and y position of the current peak
protected PVector point(float value, float angle, float x_distance, float y_distance)
{
return new PVector((int)(_x + (sin(angle) * x_distance)), (int)(_y + (cos(angle) * y_distance)));
}
}
/*
* Classic Visualization - Creates a classic style visualization in an scope style. 3 waves are created, representing mix, left and right. Left and right waves only gain 1/3 of the height of the mix wave
*/
class Classic extends Visualization
{
//The 3 waves
protected Wave _left, _right, _mix;
//Constructor
public Classic(int x, int y, int width, int height, color leftColor, color rightColor, color mixColor)
{
//Create the 3 waves
_left = new Wave(x, y, width, height/3, leftColor);
_right = new Wave(x, y, width, height/3, rightColor);
_mix = new Wave(x, y, width , height, mixColor);
}
//Draw method
public void draw(float[] left, float[] right, float[] mix, float left_level, float right_level, float mix_level)
{
//Enable smoothing
smooth();
//Draw the 3 waves
_left.draw(left);
_right.draw(right);
_mix.draw(mix);
//Disable smoothing
noSmooth();
}
}
/*
* Ecliptic Visualization - Creates a ripple of circles, who's stroke width is determined by the sound level
*/
class Ecliptic extends Visualization
{
//The increment of the visualization, the smaller this number is, the more circles will appear
final int STEP = 40;
//Properties for color, maximum stroke weight and color
protected int _x, _y, _width, _height;
protected int _weight = 0;
protected color[] _colors;
//Constructor
Ecliptic(int x, int y, int w, int h, int weight, color[] c)
{
//Assign properties
_x = x;
_y = y;
_width = w;
_height = h;
_weight = weight;
_colors = c;
}
//Draw method
public void draw(float[] left, float[] right, float[] mix, float left_level, float right_level, float mix_level)
{
//Disable fill, center circles, and enable smoothing
noFill();
ellipseMode(CENTER);
smooth();
//Counter for color
int count = 0;
//Loop through the data
for(int i = 0; i < mix.length; i+= STEP)
{
//Determine width, height and stroke color based on the iteration
int w = (int)(((float)(mix.length - i) / (float)mix.length) * _width);
int h = (int)(((float)(mix.length - i) / (float)mix.length) * _height);
stroke(_colors[count]);
//Assign stroke weight based on channel data
strokeWeight(_weight * abs(mix[i]));
//Draw the current circle
ellipse(_x, _y, w, h);
//Increment color and ensure it does not go out of bounds
count = (count + 1) % _colors.length;
}
//Disable smoothing
noSmooth();
}
}
/*
* Fading Text - A class to show the name of the visualization, which will fade quickly
*/
class FadingText
{
//Amount of alpha to reduce per frame
final int ALPHA_STEP = 20;
//Font for the fading text
protected PFont _font = loadFont("alt-40.vlw");
//Message string
public String message = "";
//Position and alpha
protected int _x, _y, _alpha = 255;
//Constructor
public FadingText(int x, int y)
{
//Assign properties
this._x = x;
this._y = y;
}
//Draw method
public void draw()
{
//Set font and align cetner
textFont(_font, 40);
textAlign(CENTER);
//Draw background
fill(50, _alpha);
rectMode(CENTER);
noStroke();
rect(APP_WIDTH / 2, _y, APP_WIDTH, 50);
//Set font color
fill(255, _alpha);
//If the fading text is still visible
if(_alpha > 0)
{
//Reduce alpha
_alpha -= ALPHA_STEP;
//Make sure alpha does not drop below 0
if(_alpha < 0) _alpha = 0;
}
//Draw the message
text(message, _x, _y + 15);
}
//Reset method
public void reset()
{
//Reset alpha to max
_alpha = 255;
}
}
/*
* Ima visualization - Produces a looping slideshow of images, who's filter values are determined by the left, right and mix channels. Each new image is faded in
*/
class Ima extends Visualization
{
//Constants for the seconds to display each image for, and amount of alpha to fade in
protected final int SLIDE_DELAY = 7;
protected final int ALPHA_STEP = 20;
//Property for the image playlist
protected Playlist _images;
//Properties for size and position
protected int _x, _y, _width, _height;
//Top and bottom PImage variables
protected PImage _top, _bottom;
//Url for the top and bottom images
protected String _top_url, _bottom_url;
//Boolean to say if the top image is to be faded
protected boolean _fade_top = false;
//Properties for the alpha value of the top image, and a counter to check the amount of seconds passed by
protected int _top_alpha = 0, _counter = 0;
//Boolean to determine if a fatal error occurred
protected boolean _fatal_error = false;
//Counter of invalid images
protected int _invalid_images = 0;
//Constructor
public Ima(int x, int y, int w, int h, Playlist images)
{
//Assign properties
_x = x;
_y = y;
_width = w;
_height = h;
_images = images;
//If the playlist was errornous
if(_images.fatalError())
{
//A fatal error occurred
_fatal_error = true;
}
else
{
//Get the url of the first image
_bottom_url = _images.get(0);
//Whilst the bottom image is invalid
while(!validateImage(_bottom_url))
{
//Increase the invalid counter
_invalid_images++;
//If all images are invalid
if(_invalid_images == _images.size())
{
//A fatal error occurred and break from loop
_fatal_error = true;
println("No valid image files");
break;
}
//Try to get the next image
_bottom_url = _images.next();
}
//If a fatal error occurred or the playlist had an error
if(_fatal_error || _images.fatalError())
{
//A fatal error occurred - Used because of checking the playlist message
_fatal_error = true;
}
else
{
//Load the next image url into the top
_top_url = _images.next();
//Load images
load();
}
}
}
//Draw method
public void draw(float[] left, float[] right, float[] mix, float left_level, float right_level, float mix_level)
{
//If a fatal error occurred, just show an error message
if(_fatal_error)
{
textFont(g_title_font, 32);
fill(150);
textAlign(CENTER);
text("Could not load images", _x, _y);
}
else
{
//If the next image needs to be shown - the !_fade_top test will prevent uncessary processing
if(!_fade_top && (int)(_counter / frameRate) == SLIDE_DELAY)
{
//Start to fade in
_fade_top = true;
}
//If the top image needs to be faded in
if(_fade_top)
{
//Increase alpha of top image
_top_alpha += ALPHA_STEP;
}
//Set images to draw in the center
imageMode(CENTER);
//Grab the red, green and blue channels for the filter based on the channels
float r = 100 + left_level * 255;
if(r > 255) r = 255;
float g = 100 + right_level * 255;
if(g > 255) g = 255;
float b = 100 + mix_level * 255;
if(b > 255) b = 255;
//Tint the image
tint(r, g, b);
//Draw the bottom image
image(_bottom, _x, _y);
//Tint the image, applying the alpha of the top image
tint(r, g, b, _top_alpha);
//Draw the top image
image(_top, _x, _y);
//If the top image has fully faded in
if(_top_alpha >= 255)
{
//Stop the fade animation, hide the top image, reset the counter, and get the next image
_fade_top = false;
_top_alpha = 0;
next();
_counter = 0;
}
//Increment counter for fade
_counter++;
}
}
//Reset method
public void reset()
{
//Reset counter to 0
_counter = 0;
}
//Loads the front image
protected PImage loadFront()
{
return loadImage(_top_url);
}
//Loads the back image
protected PImage loadBack()
{
return loadImage(_bottom_url);
}
//Get the next set of images
protected void next()
{
//Get the next image
String url = _images.next();
//Whilst the image is invalid, attempt to get a new image (will never run an unlimited loop as this is checked in the constructor)
while(!validateImage(url)) url = _images.next();
//Assign bottom image to the current top image
_bottom_url = _top_url;
//Set the top image to the new image
_top_url = url;
load();
}
//Load new images
protected void load()
{
//If the top image exists, simply use that as the bottom image, otherwise load a new PImage
_bottom = _top == null ? loadBack() : _top;
//Load the new top image
_top = loadFront();
//Resize both images to the size of the visualization
_top.resize(_width, _height);
_bottom.resize(_width, _height);
}
//Method to validate an image
protected boolean validateImage(String url)
{
try
{
PImage t = loadImage(url);
if(t == null) return false;
}
catch(Exception e)
{
return false;
}
return true;
}
}
/****************************************************************************/
/* ImaViz Application - Ashley Blurton */
/* Coursework for Animation and Multimedia (CSC-10026) */
/****************************************************************************/
//Imports
import ddf.minim.*;
import java.awt.*;
import javax.swing.*;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Properties;
/*********** CONSTANTS **********/
final color BACKGROUND = color(0); //Background color of applet
final int APP_WIDTH = 480, APP_HEIGHT = 480; //Application dimensions
final int APP_FRAMERATE = 20; //Framerate of application. Set to 20 as visualizations utilize a lot of processing
final int BUFFER_SIZE = 1024; //Amount of bytes to buffer into each channel of the AudioPlayer
final int HELP_ALPHA_STEP = 50; //Amount of alpha to decrease/increase per frame for help menu
final int MAX_URL = 30; //Maximum amount of characters in a URL before shortening occurs
final String MENU_MUSIC = "menu.mp3"; //Path to the menu music in the /data folder
//Color constants for use with theme arrays
final color WHITE = color(255);
final color RED = color(255, 0, 0);
final color GREEN = color(0, 255, 0);
final color BLUE = color(0, 0, 255);
final color YELLOW = color(255, 255, 0);
final color PURPLE = color(255, 0, 255);
final color TEAL = color(0, 255, 255);
final color ORANGE = #FF9900;
//Raw color constants for use with visualizations that need to set alpha values
final int[] RAW_WHITE = {255, 255, 255};
final int[] RAW_RED = {255, 0, 0};
final int[] RAW_GREEN = {0, 255, 0};
final int[] RAW_BLUE = {0, 0, 255};
final int[] RAW_YELLOW = {255, 255, 0};
final int[] RAW_PURPLE = {255, 0, 255};
final int[] RAW_TEAL = {0, 255, 255};
final int[] RAW_ORANGE = {255, 153, 0};
//Themes for visualizations
final color[] WARM = {YELLOW, ORANGE, RED};
final color[] COOL = {TEAL, BLUE, WHITE};
final color[] DEFAULT = {PURPLE, TEAL, BLUE, GREEN, YELLOW, RED};
//Raw themes for visualizations that use alpha
final int[][] RAW_WARM = {RAW_YELLOW, RAW_ORANGE, RAW_RED};
final int[][] RAW_COOL = {RAW_TEAL, RAW_BLUE, RAW_WHITE};
final int[][] RAW_DEFAULT = {RAW_BLUE, RAW_TEAL, RAW_GREEN, RAW_YELLOW, RAW_RED, RAW_PURPLE};
//Theme indexes for settings
final int DEFAULT_THEME = 0; //Uses a rainbow based color scheme
final int WARM_THEME = 1; //Uses a warm tone color scheme
final int COOL_THEME = 2; //Uses a cool tone color scheme
//State constants
final int PLAY_ERROR = -4; //When no mp3 can be played
final int PLAYLIST_ERROR = -3; //When the playlist cannot be loaded, or is empty.
final int FONT_ERROR = -2; //When fonts cannot be loaded into the system
final int MINIM_ERROR = -1; //If minim cannot be initialized
final int INIT = 0; //First state of the application before the main menu is shown
final int INIT_PLAYBACK = 1; //State before playback begins, loads the playlist and sets up visuals
final int PLAYING = 2; //When music is being played, allows changing of visualization and tracks
final int MAIN_MENU = 3; //Shows the main menu allowing the user to start, edit options, view credits, or exit.
final int OPTIONS = 4; //Allows users to set the theme, xml playlist locations, and whether or not to use images
final int CREDITS = 5; //Shows the credits menu
final int THEME_SELECT = 6; //Shows the menu to select the theme
final int IMAGE_MODE_SELECT = 7; //Shows the menu to select image use
final int ABOUT = 8; //Applet version
final int EXIT = 9; //Turns off all visualizations and menus, and displays the exit message.
/*********** END CONSTANTS **********/
/*********** GLOBAL PROPERTIES ***********/
//Current theme property
int g_theme = DEFAULT_THEME;
//Default location of xml playlists
String g_music = "mp3.xml";
String g_images = "images.xml";
//Help display properties
boolean showHelp = false; //If help is being displayed
int alphaHelp = 0; //Alpha of the help menu
//Counters for files that cannot be loaded as audio
int corruptSongs = 0, corruptNotify = 0;
//Visualization name display
FadingText g_visual;
//Music variables
AudioMetaData g_meta; //Stores ID3 info in mp3 files
AudioPlayer g_player; //Plays the mp3 files in playback mode
AudioPlayer g_menu_player; //Plays the menu music
Minim g_minim; //Minim instance
//Current state
int state = INIT; //Start within in the initialize phase
//Playlist for mp3 files
Playlist g_playlist;
//Menus
Menu g_main_menu; //Main menu
Menu g_credits; //Credits menu
Menu g_options; //Options menu
Menu g_theme_select; //Theme select menu
Menu g_image_mode_select; //Use images menu
Menu g_current_menu; //Tracks the currently displayed menu - Used for user input
Menu g_about; //Shows information about the applet version
//Visualization lists
LinkedList g_visuals = new LinkedList(); //List for storing visualizations
LinkedList g_visuals_name = new LinkedList(); //List for storing the names of visualizations
//Menu visualization item
Visualization g_menu_visual;
//Image visualization item
Ima g_image_visual;
//Image show visual toggle
boolean g_show_images = false;
//Current menu visualization
int g_menu_visual_index = 0;
//Tracker for last selected() menu item - Used in mouseRelease/mousePress to circumvent mouseClick which will not fire when the mouse is moved slightly between press and release
int g_last_selected = 0;
//Current visualization index
int g_current_visual_index = 0;
Visualization g_current_visual; //Stores a pointer to the current visualization, since access to linked list can slow
//Fonts
PFont g_font, g_title_font;
/*********** END GLOBAL PROPERTIES ***********/
//Method to make setUpVisuals use true by default
void setUpVisuals() { setUpVisuals(true); };
//Method to setup visualizations
void setUpVisuals(boolean renew)
{
//Arrays to store the colors to be used
color[] c;
int[][] rawC;
//Assign colors based on current theme
switch(g_theme)
{
case WARM_THEME:
c = WARM;
rawC = RAW_WARM;
break;
case COOL_THEME:
c = COOL;
rawC = RAW_COOL;
break;
default:
c = DEFAULT;
rawC = RAW_DEFAULT;
break;
}
//Setup triwave/classic colors as they can only use 3 colors, where as the default theme has 6
color[] triwave = new color[3];
triwave[0] = c.length == 3 ? c[0] : c[1];
triwave[1] = c.length == 3 ? c[1] : c[3];
triwave[2] = c.length == 3 ? c[2] : c[5];
//Reset visuals lists
g_visuals = new LinkedList();
g_visuals_name = new LinkedList();
//Determine the smallest dimension
float smallest = width < height ? width : height;
//Create TriWave visual
g_visuals.add(new TriCircleWave(width / 2, height / 2, (int)(0.8 * smallest) , (int)(0.8 * smallest), (int)(smallest / 10) , triwave[2], triwave[1], triwave[0], 2));
g_visuals_name.add("TriWave");
//Create Ecliptic visual
g_visuals.add(new Ecliptic(width /2, height / 2, (int)(0.9 * smallest) , (int)(0.9 * smallest), 12, c));
g_visuals_name.add("Ecliptic");
//Create Blitz visual
g_visuals.add(new Blitz(width /2, height / 2, width, (int)(0.8 * height), 40, c));
g_visuals_name.add("Blitz");
//Create Pulsar visual
g_visuals.add(new Pulsar(width /2, height / 2, (int)(0.9 * smallest) , (int)(0.9 * smallest), 50, rawC));
g_visuals_name.add("Pulsar");
//Create Burst visual
g_visuals.add(new Burst(width / 2, height / 2, (int)(0.9 * smallest), c));
g_visuals_name.add("Burst");
//Create Chaos visual
g_visuals.add(new Chaos(width / 2, height / 2, (int)(0.9 * smallest) , (int)(0.9 * smallest), rawC));
g_visuals_name.add("Chaos");
//Create Torrent visual
g_visuals.add(new Torrent(width / 2, height / 2, (int)(0.9 * smallest) , (int)(0.9 * smallest), rawC));
g_visuals_name.add("Torrent");
//Create Classic Visual
g_visuals.add(new Classic(width / 2, height / 2, width, (int)(0.8 * height), triwave[0], triwave[1], triwave[2]));
g_visuals_name.add("Classic");
//Set up image visuals
setUpImageVisual(false);
//Set message of visual name display
g_visual.message = (String)g_visuals_name.get(0);
//Menu visualization setup
if(renew) g_menu_visual_index = (int)random(g_visuals.size()); //If renew is true, change the visual index
g_menu_visual = (Visualization)g_visuals.get(g_menu_visual_index);//Random visualization
//Assign current visual
g_current_visual = (Visualization)g_visuals.get(g_current_visual_index);
}
//Method to setup image visuals
void setUpImageVisual(boolean renew)
{
//Load playlist
Playlist images = new Playlist(this, g_images);
//Create new Ima instance if it is empty, or renew has been specified
if(g_image_visual == null || renew) g_image_visual = new Ima(width / 2, height / 2 , width, height, images);
else g_image_visual.reset(); //Otherwise just call reset
}
//Setup method - ran on application start
void setup()
{
//First time setup
if(state == INIT)
{
//Set size of the application
size(APP_WIDTH, APP_HEIGHT);
//Set background color
background(BACKGROUND);
//Set framerate
frameRate(APP_FRAMERATE);
//Setup minim
try
{
g_minim = new Minim(this); //Attempt to intialize minim
}
catch (Exception e)
{
state = MINIM_ERROR; //Set state to minim error if minim could not be started
}
//Load fonts
try
{
//Generic font
g_font = loadFont("font-20.vlw");
//Font for menu titles
g_title_font = loadFont("font-alt-32.vlw");
//Setup font
textFont(g_font, 20);
}
catch (Exception e)
{
state = FONT_ERROR; //Go to font error if fonts could not be loaded
}
/************** MENU SETUP **************/
MenuItem[] main_menu = {new MenuButton("Start"), new MenuButton("Options"), new MenuButton("Credits"), new MenuButton("About the Applet Version"), new MenuButton("Exit")};
g_main_menu = new Menu("ImaViz", main_menu, width / 2, height / 2);
MenuItem[] credits = {new MenuText("Menu Music:"), new MenuButton("http://www.newgrounds.com/audio/listen/302626"), new MenuText("Coded by: Ashley Blurton"), new MenuText("Designed by: Ashley Blurton"), new MenuText("Made for: CSC-10026"), new MenuButton("Main Menu")};
g_credits = new Menu("Credits", credits, width / 2 , height / 2);
g_options = new Menu("Options", configOptions(), width / 2, height / 2);
MenuItem[] themes = {new MenuButton("Normal"), new MenuButton("Warm"), new MenuButton("Cool"), new MenuButton("Back to Options")};
g_theme_select = new Menu("Theme Select", themes, width / 2, height / 2);
MenuItem[] image_options = {new MenuButton("Yes"), new MenuButton("No"), new MenuButton("Back to Options")};
g_image_mode_select = new Menu("Use images", image_options, width / 2, height / 2);
MenuItem[] about_options = {
new MenuText("Unfortunately due to restrictions with applets, the full version of the application cannot be published online. So features have been removed:"),
new MenuText(""),
new MenuText("Custom playlists have been removed, and only a selection of three 30 second tracks can be played, and the image slideshow can only use pre-determined images."),
new MenuText(""),
new MenuText("You can get the full version below (click):"),
new MenuButton("http://media.deathmonkeyz.com/"),
new MenuButton("Main Menu")
};
g_about = new Menu("About", about_options, width / 2, height / 2);
/************** END MENU SETUP **************/
//Visualization display set-up
g_visual = new FadingText(width / 2, height / 2);
//Visualization set-up
setUpVisuals();
//If state is INIT (i.e no errors occurred) go to the main menu
if(state == INIT)
state = MAIN_MENU;
}
}
//Draw method (called every frame)
void draw()
{
//Clear screen
background(BACKGROUND);
switch(state)
{
//If errors were encountered when trying to use the playlist
case PLAY_ERROR:
drawErrorDialogue("Playback Error", "Could not load the mp3 file.", "Q: Main Menu\nO: Options");
break;
case PLAYLIST_ERROR:
drawErrorDialogue("Playlist Error", g_playlist.message(), "Q: Main Menu\nO: Options");
break;
case MINIM_ERROR:
drawErrorDialogue("Sound Library Error", "Could not start the sound library", "Please restart the application");
break;
case FONT_ERROR:
print("FONT ERROR");
break;
case INIT_PLAYBACK:
//Setup mp3 playlist
g_playlist = new Playlist(this, g_music);
//Set the current state to playing
state = PLAYING;
//Reset corrupt counter
corruptNotify = 0;
//If no errors were encountered whilst loading the playlist
if(!g_playlist.fatalError())
{
//Load first track
load(0);
}
else
{
//Display error message in console
println(g_playlist.message());
//Show playlist error message
state = PLAYLIST_ERROR;
}
//If the menu player was created and is still playing, pause it.
if(g_menu_player != null && g_menu_player.isPlaying()) g_menu_player.pause();
//Close help menu and reset alpha to 0
showHelp = false;
alphaHelp = 0;
break;
case MAIN_MENU:
g_current_menu = g_main_menu; //Set the current menu to the main menu
break;
case CREDITS:
g_current_menu = g_credits; //Set the current menu to the credits
break;
case THEME_SELECT:
g_current_menu = g_theme_select; //Set the current menu to the theme select menu
break;
case IMAGE_MODE_SELECT:
g_current_menu = g_image_mode_select; //Set the current menu to the use images menu
break;
case OPTIONS:
g_options.setItems(configOptions()); //Generate the fields for the options menu
g_current_menu = g_options; //Set the current menu to the options menu
break;
case ABOUT:
g_current_menu = g_about;
break;
case PLAYING:
//If the player has been initialized
if(g_player != null)
{
//Draw images in the background if the option is enabled
if(g_show_images) g_image_visual.draw(g_player.left.toArray(), g_player.right.toArray(), g_player.mix.toArray(), g_player.left.level(), g_player.right.level(), g_player.mix.level());
//Draw the current visualization
g_current_visual.draw(g_player.left.toArray(), g_player.right.toArray(), g_player.mix.toArray(), g_player.left.level(), g_player.right.level(), g_player.mix.level());
//If the player has stopped (i.e song has finished) play the next track automatically
if(!g_player.isPlaying())
{
loadNext();
}
}
else
{
//Silent visualization if the player is not set
float[] blank = new float[BUFFER_SIZE];
if(g_show_images) g_image_visual.draw(blank, blank, blank, 0, 0, 0);
g_current_visual.draw(blank, blank, blank, 0, 0, 0);
}
//Setup g_font
textFont(g_font, 20);
smooth();
//Output track info
if(g_meta != null)
{
//White text
fill(255);
textAlign(LEFT);
//Output title and artist info in top left
text("Title: " + g_meta.title(), 5, 15);
text("Artist: " + g_meta.author(), 5, 30);
String total = "";
//If the length could not be determined, display an unknown length
if(g_player.length() == -1)
total = "??:??";
else
total = formatTime(g_player.length()); //Get a formatted time
//Display current position and total time in lower right
textAlign(RIGHT);
text(formatTime(g_player.position()) + " / " + total, width - 5, height - 5);
}
//If playlist loaded correctly
if(!g_playlist.fatalError())
{
fill(255);
textAlign(RIGHT);
//Output playlist location in top right
text("Playlist: " + shorten(g_playlist.url()), width - 5, 15);
//Display current track and total tracks. Including any corrupt tracks if any.
text("Track: " + g_playlist.positionMessage() + (corruptNotify == 0 ? "" : " (" + corruptNotify + " Errors)"), width - 5, 30);
}
//Output help message in lower left
fill(255);
textAlign(LEFT);
text("View Help: H", 5, height - 5);
//Show visualization name
g_visual.draw();
//Fade In/Out help
if(showHelp && alphaHelp < 255)
alphaHelp += HELP_ALPHA_STEP;
else if(!showHelp && alphaHelp > 0)
alphaHelp -= HELP_ALPHA_STEP;
//Ensure alpha value is within bounds
alphaHelp = alphaHelp > 255 ? 255 : (alphaHelp < 0 ? 0 : alphaHelp);
/************* HELP DISPLAY ***********/
textFont(g_font, 20);
stroke(255, alphaHelp);
strokeWeight(1);
rectMode(CENTER);
fill(0, 200*((float)alphaHelp/255));
rect(width / 2, height / 2, 300, 240);
fill(255, alphaHelp);
textAlign(RIGHT);
text("LEFT ARROW:\nRIGHT ARROW:\nUP ARROW\nDOWN ARROW:\nLEFT CLICK:\nRIGHT CLICK:\nH KEY:\nT KEY:\nI KEY:\nQ KEY:", width/2 - 30, height/2 - 60);
textAlign(LEFT);
text("Previous Track\nNext Track\nNext Visualization\nPrevious Visualization\nNext Visualization\nNext Track\nShow/Hide Help\nTheme Toggle (Not Saved)\nImage Toggle (Not Saved)\nMain Menu", width/2-20, height/2 - 60);
textAlign(CENTER);
text("Created by Ashley Blurton", width/2, height/2 - 90);
text("Coursework for CSC-10026", width/2, height/2 + 105);
/************ END HELP DISPLAY *********/
noSmooth();
break;
case EXIT:
//Stop any players that may be running
if(g_player != null && g_player.isPlaying()) g_player.pause();
if(g_menu_player != null && g_menu_player.isPlaying()) g_menu_player.pause();
//Output an exit message
textAlign(CENTER);
fill(255);
textFont(g_title_font, 32);
text("Thanks for using ImaViz!!!", width / 2, height / 2);
textFont(g_font, 20);
text("To download the full version. Press ENTER to open the download page", width / 2, height / 2 + 30);
break;
}
//Show menus if within a menu state
if(state >= MAIN_MENU && state < EXIT)
{
showMenu(g_current_menu);
}
}
//Method to display a menu
void showMenu(Menu m)
{
//Turn off the main AudioPlayer
if(g_player != null && g_player.isPlaying()) g_player.pause();
//Attempt to load the menu music if it has stopped or was never created
if(g_menu_player == null || !g_menu_player.isPlaying())
{
try
{
g_menu_player = g_minim.loadFile(MENU_MUSIC, BUFFER_SIZE);
g_menu_player.play();
//Loop the menu music indefinitely
g_menu_player.loop();
}
catch(Exception e){}
}
//If the menu music is playing, display the menu visuals
if(g_menu_player != null) {
if(g_show_images) g_image_visual.draw(g_menu_player.left.toArray(), g_menu_player.right.toArray(), g_menu_player.mix.toArray(), g_menu_player.left.level(), g_menu_player.right.level(), g_menu_player.mix.level()); //Display images if the options is enabled
g_menu_visual.draw(g_menu_player.left.toArray(), g_menu_player.right.toArray(), g_menu_player.mix.toArray(), g_menu_player.left.level(), g_menu_player.right.level(), g_menu_player.mix.level());
}
//Draw the menu provided
m.draw();
//Display navigation hint message in bottom center
fill(255);
textAlign(CENTER);
textFont(g_font, 20);
text("Use arrow keys to navigate, and SPACE to select. Or use the mouse.", width / 2, height - 5);
text("Applet Version.", width / 2, 15); //Applet version notification
}
//Method to shorten url strings over a certain length
String shorten(String s)
{
String r = ""; //Return string
//If the string needs shortening
if(s.length() > MAX_URL)
{
r = s.substring(0, (MAX_URL-3)/2) + "..." + s.substring(s.length()-(MAX_URL-3)/2); //Select first set of characters, append ... and then add the final few characters
}
else
return s; //Return the original string if it is too short
return r;
}
//Method to dynamically generate the option menu items based on current configuration
MenuItem[] configOptions()
{
String cTheme = g_theme == WARM_THEME ? "Warm" : (g_theme == COOL_THEME ? "Cool" : "Normal"); //Theme message
MenuItem[] r = {
new MenuButton("Theme: " + cTheme),
new MenuButton("Show Images: " + (g_show_images ? "Yes" : "No")),
new MenuText("------------"),
new MenuText("WARNING:"),
new MenuText("ID3v2 tags in mp3s not supported correctly"),
new MenuText("Use kid3 to remove them"),
new MenuButton("http://kid3.sourceforge.net/"),
new MenuText("------------"),
new MenuButton("Main Menu")
};
return r;
}
//Method to display a simplistic error dialogue
void drawErrorDialogue(String title, String message, String instructions)
{
//Draw bounding rectangle
stroke(255);
strokeWeight(1);
fill(25);
rectMode(CENTER);
rect(width / 2, height / 2, 300, 200);
fill(255);
textAlign(CENTER);
//Draw error title
textFont(g_title_font, 32);
text(title, width / 2, height / 2 - 60);
//Draw error message
textFont(g_font, 20);
text(message, width /2, height / 2);
//Draw error instructions
text(instructions, width /2, height / 2 + 60);
}
//Show next visual
void nextVisual()
{
//Increase current visual and loop
g_current_visual_index++;
g_current_visual_index %= g_visuals.size();
loadVisual();
}
//Show previous visual
void prevVisual()
{
//Decrease current visual and loop
g_current_visual_index = g_current_visual_index == 0 ? g_visuals.size() - 1 : g_current_visual_index - 1;
loadVisual();
}
//Load visual
void loadVisual()
{
//Show visualization name
g_visual.message = (String)g_visuals_name.get(g_current_visual_index);
g_visual.reset();
//Reset visualization
g_current_visual = (Visualization)g_visuals.get(g_current_visual_index);
g_current_visual.reset();
}
//Attempt to load next song
void loadNext()
{
while(!load(g_playlist.next()));
}
//Attempt to load previous song
void loadPrev()
{
while(!load(g_playlist.prev()));
}
//Manually load a file from a specific position
void load(int count)
{
//If the desired item cannot be loaded, attempt the next track
if(!load(g_playlist.get(count)))
loadNext();
}
//Method to load an mp3
boolean load(String file)
{
//If the g_player exists, try to close it before loading a new file
if(g_player != null)
{
try {
g_player.close();
}
catch (Exception e){}; //g_player can sometimes provide a null pointer exception, this will ignore it
}
//Reset meta data
g_meta = null;
//If the playlist has no errors
if(!g_playlist.fatalError())
{
try
{
//Attempt to load and play the file
g_player = g_minim.loadFile(file, BUFFER_SIZE);
g_player.play();
//Access the meta data of the file
g_meta = g_player.getMetaData();
//Reset the corrupt song counter
corruptSongs = 0;
}
catch(Exception e)
{
//If it could not load a file increase the corrupt song notification
corruptSongs++;
corruptNotify = corruptSongs; //Corrupt notify is used when displaying the amount of errors. As corruptSongs is reset upon successful load
//If every song was found to be corrupt
if(corruptSongs == g_playlist.size()) {
corruptSongs = 0;
state = PLAY_ERROR; //Show a play error
}
else return false;
}
}
else
{
//If the playlist contained errors. Go to the playlist error dialogue
println(g_playlist.message());
state = PLAYLIST_ERROR;
}
return true;
}
//String to turn a millisecond time into a mm:ss format
String formatTime(int time)
{
//Get the total seconds (rounded down)
time /= 1000;
//Get the current minutes and convert to a string
String minutes = Integer.toString(time / 60);
//Prepend a 0 if lower than 10
minutes = Integer.parseInt(minutes) < 10 ? "0" + minutes : minutes;
//Get left over seconds after minutes are calculated
String seconds = Integer.toString(time % 60);
//Prepend 0 if less than 10
seconds = Integer.parseInt(seconds) < 10 ? "0" + seconds : seconds;
//Return the formatted string
return minutes + ":" + seconds;
}
//Handle keyReleases. KeyPressed fires multiple times if a key is held down, so it is best not to use it
void keyReleased()
{
//Handle input based on current state
switch(state)
{
//If it is an error
case PLAY_ERROR:
case PLAYLIST_ERROR:
if(key != CODED)
{
switch(key)
{
//Uppercase and lower case are treated as separate inputs, so process both
case 'Q':
case 'q':
//Return to the main menu
state = MAIN_MENU;
break;
case 'o':
case 'O':
//Return to the options menu
state = OPTIONS;
break;
}
}
break;
case PLAYING:
if(key == CODED)
{
switch(keyCode)
{
//Previous track key
case LEFT:
loadPrev();
break;
//Next track key
case RIGHT:
loadNext();
break;
//Next visualization key
case UP:
nextVisual();
break;
//Previous visualization key
case DOWN:
prevVisual();
break;
}
}
else
{
switch(key)
{
//Help toggle
case 'h':
case 'H':
showHelp = !showHelp; //Flip boolean
break;
case 'Q':
case 'q':
if(g_menu_player != null) g_menu_player.cue(0);
state = MAIN_MENU;
break;
//Image toggle
case 'i':
case 'I':
g_show_images = !g_show_images; //Flip images boolean
break;
//Theme toggle
case 't':
case 'T':
switch(g_theme)
{
case WARM_THEME:
g_theme = COOL_THEME;
break;
case COOL_THEME:
g_theme = DEFAULT_THEME;
break;
case DEFAULT_THEME:
g_theme = WARM_THEME;
break;
}
//Reset visuals to reflect theme change
setUpVisuals(false);
break;
}
}
break;
case EXIT:
if(key == ENTER || key == RETURN)
{
link("http://media.deathmonkeyz.com/ImaViz/download.html"); //Open page to full version
}
break;
}
//If a menu is showing
if(state >= MAIN_MENU && state < EXIT)
{
//Process keyboard navigation
if(key == CODED)
{
handleNavigation(g_current_menu, keyCode);
}
//If the user selected() an item
else if (key == ' ')
{
handleSelect(g_current_menu);
}
}
}
//Method to handle navigation based on mouse movement and button presses
void handleNavigation(Menu m, int x, int y)
{
//Use the updateFromMouse method of the menu class
m.updateFromMouse(x, y);
}
//Method to handle navigation based on key presses
void handleNavigation(Menu m, int keyCode)
{
switch(keyCode)
{
case UP:
m.prev();
break;
case DOWN:
m.next();
break;
}
}
//Method to handle when a menu item is selected()
void handleSelect(Menu m)
{
//If the main menu was used
if(m.equals(g_main_menu))
{
//Determine which item was selected()
switch(m.selected())
{
//Start visuals
case 0:
state = INIT_PLAYBACK;
break;
//Go to Options
case 1:
state = OPTIONS;
break;
//Go to Credits
case 2:
state = CREDITS;
break;
//Go to About
case 3:
state = ABOUT;
break;
//Go to Exit
case 4:
state = EXIT;
break;
}
}
//Options menu
else if(m.equals(g_options))
{
//Does not use CASE since the final option index is dynamically generated
//Theme selection
if(m.selected() == 0)
{
state = THEME_SELECT; //Change state
}
//Use images
else if(m.selected() == 1)
{
state = IMAGE_MODE_SELECT; //Change state
}
//Kid3 application
else if(m.selected() == m.count()-3)
{
//Open the kid3 website in a new tab/window
link("http://kid3.sourceforge.net", "_new");
}
//Back to menu
else if(m.selected() == m.count()-1)
{
state = MAIN_MENU; //State change
}
;
}
//Theme select menu
else if(m.equals(g_theme_select))
{
//Back to options
if(m.selected() == m.count()-1)
{
state = OPTIONS; //Change state
}
else
{
//Change theme
g_theme = m.selected();
//Restart visuals to reflect change
setUpVisuals(false);
}
;
}
//Use images menu
else if(m.equals(g_image_mode_select))
{
switch(m.selected())
{
case 0:
g_show_images = true; //Enable images
break;
case 1:
g_show_images = false; //Disable images
break;
case 2:
state = OPTIONS; //Return to options
break;
}
;
}
//Credits menu
else if(m.equals(g_credits))
{
switch(m.selected())
{
//Menu music
case 1:
link("http://www.newgrounds.com/audio/listen/302626", "_new"); //Open page to menu music in a new tab/window
break;
//Main menu
case 5:
state = MAIN_MENU; //Change state
break;
}
}
else if(m.equals(g_about))
{
if(m.selected() == m.count()-2)
{
link("http://media.deathmonkeyz.com/ImaViz/download.html", "_new"); //Open page to full version
}
else if(m.selected() == m.count()-1)
{
state = MAIN_MENU; //Change state
}
}
}
//When the mouse is pressed
void mousePressed()
{
//If in a menu, store the currently selected() menu item (used to circumvent mouseClicked)
if(state >= MAIN_MENU && state < EXIT && mouseButton == LEFT)
{
handleNavigation(g_current_menu, mouseX, mouseY);
g_last_selected = g_current_menu.selected();
}
}
//When the mouse is released
void mouseReleased()
{
//Ensure the mouse button is the left
if(mouseButton == LEFT)
{
if(state >= MAIN_MENU && state < EXIT)
{
//Ensure the mouse has not moved to a different menu item
handleNavigation(g_current_menu, mouseX, mouseY);
if(g_last_selected != g_current_menu.selected())
return;
//Process selection
handleSelect(g_current_menu);
}
else if (state == PLAYING)
{
nextVisual(); //Load next visualization
}
}
//If the the screen was right clicked
else if(mouseButton == RIGHT)
{
//If the application is playing files, load the next track
if(state == PLAYING) loadNext();
}
}
//When the mouse mouse is moved
void mouseMoved()
{
//If in a menu state, handle selection
if(state >= MAIN_MENU && state < EXIT)
{
handleNavigation(g_current_menu, mouseX, mouseY);
}
}
//Run when application is closed
void stop()
{
//Try to shut down any players and minim instances
//Try catch around all statements prevents any issues caused by earlier errors within execution
try
{
if(g_menu_player != null) g_menu_player.close();
}catch (Exception e){};
try
{
if(g_player != null) g_player.close();
}catch (Exception e){};
try
{
if(g_minim != null) g_minim.stop(); //Close minim interface
} catch (Exception e){};
super.stop(); //Parent stop
}
/*
* Menu - Class for creating UI menus that can be navigated keyvboard or mouse
*/
class Menu {
//Config consts
public final static int MENU_WIDTH = 300;
public final static int TITLE_HEIGHT = 65;
//Title of menu
protected String _title;
//Array list of MenuItems
protected MenuItem[] _items;
//Currently selected item
protected int _selected = -1;
//Position
protected int _x = 0;
protected int _y = 0;
//height
int _height;
//Constructor
public Menu(String title, MenuItem[] items, int x, int y)
{
//Assign properties
_title = title;
_x = x;
_y = y;
//Select first item by default
_selected = 0;
//Assign menu items
setItems(items);
}
//Returns the amount of menu items
public int count()
{
return _items.length;
}
//Returns the selected index
public int selected()
{
return _selected;
}
//Method to update the menu via the mouse
public void updateFromMouse(int mouseX, int mouseY)
{
//Starting y position
int yPos = _y - (_height / 2) + 50; //Start y position
//Select no item by default
_selected = -1;
//Loop through items
for(int i = 0; i < _items.length; i++)
{
//If the item is selectable and it's updateFroMouseMethod returns true
if(_items[i].selectable() && ((SelectableMenuItem)_items[i]).updateFromMouse(_x, yPos + _items[i].height()/2, MENU_WIDTH, mouseX, mouseY))
{
//Set the currently selected item to the menu items index
_selected = i;
//Exit the loop
break;
}
//Increase the y position by the processed element's height
yPos += _items[i].height();
}
}
//Method to select an item manually
public void select(int index)
{
if(index < 0 || index > _items.length - 1) return;
_selected = index;
//If the item is not selectable try to get the next item
if(!_items[_selected].selectable()) next();
}
//Method to select the next item
public void next()
{
//If no item is selected, default to selecting the first item
if(_selected == -1)
{
_selected = 0;
}
else
{
//Get the currently selected item
int previous = _selected;
//Whilst the next item cannot be selected
while(!_items[(_selected = ++_selected % _items.length)].selectable())
{
//If all elements have been processed, exit the loop
if(_selected == previous) break; //If no selectable elements were found
}
}
}
//Method to select the previous item
public void prev()
{
//If no item is selected, default to selecting the last item
if(_selected == -1)
{
_selected = count()-1;
}
else
{
//Get the currently selected item
int previous = _selected;
//Whilst the previous item cannot be selected
while(!_items[(_selected = _selected == 0 ? _items.length - 1 : _selected - 1)].selectable())
{
//If all elements have been processed, exit the loop
if(_selected == previous) break; //If no selectable elements were found
}
}
}
//Method to assign the menu items
public void setItems(MenuItem[] items)
{
//Store them in the array
_items = items;
_height = TITLE_HEIGHT; //Minimum height for title
//Add to the menu height per item
for(int i = 0; i < items.length; i++)
{
_height += items[i].height();
}
}
//Draw method
public void draw()
{
//Draw rectangles from the center
rectMode(CENTER);
//Set stroke and fill
stroke(255);
strokeWeight(2);
fill(25, 200);
//Draw menu box
rect(_x, _y, MENU_WIDTH, _height);
//Set text options for title
fill(255);
textAlign(CENTER);
textFont(g_title_font, 32);
//Calculate the starting y position
int startY = _y - (_height / 2) + 30;
//Draw title
text(_title, _x, startY);
//Add padding for first item
int yPos = startY + 20;
//Loop through items
for(int i = 0; i < _items.length; i++)
{
//If the item is selectable
if(_items[i].selectable())
{
//If this item is selected, select it
if(_selected == i)
((SelectableMenuItem)_items[i]).select();
//Otherwise deselect it
else
((SelectableMenuItem)_items[i]).deselect();
}
//Draw the item
_items[i].draw(_x, yPos + _items[i].height()/2, MENU_WIDTH);
//Increase the y position based on processed element's height
yPos += _items[i].height();
}
}
}
/*
* MenuButton - Class to represent selectable and clickable buttons in a menu
*/
class MenuButton extends SelectableMenuItem
{
//Button height
final int HEIGHT = 35;
final color HIGHLIGHT_COLOR = color(100, 200);
final color TEXT_COLOR = color(255);
//Message
protected String _message;
//Constructor
public MenuButton(String message)
{
//Assign properties
_selectable = true;
_height = HEIGHT;
_message = message;
}
//Draw the element
public void draw(int x, int y, int width)
{
//If the item is selected, draw the highlight
if(_selected)
{
noStroke();
fill(HIGHLIGHT_COLOR);
rect(x, y, width-2, height());
}
//Draw the button text
fill(TEXT_COLOR);
textFont(g_font, 20);
textAlign(CENTER);
text(_message, x, y + 5);
}
//Mouse update
public boolean updateFromMouse(int x, int y, int width, int mouseX, int mouseY)
{
return mouseX >= x - (width-2) / 2 && mouseX <= x + (width-2)/2 && mouseY >= y - HEIGHT/2 && mouseY <= y + HEIGHT/2;
}
}
/*
* Abstract class for menu items
*/
abstract class MenuItem
{
//Properties
protected boolean _selectable = false; //If the item can be highlighted/selected via keys or the mouse
protected int _height; //Height of the element
//Method to return if the item is selectable - cannot be re-written
public final boolean selectable()
{
return _selectable;
}
//Method to return the height of the element - cannot be re-written
public final int height()
{
return _height;
}
//Method which elements must use
abstract public void draw(int x, int y, int width);
}
/*
* MenuText - Class to respresent standard text in a menu that cannot be selected
*/
class MenuText extends MenuItem
{
//Button height
protected final int HEIGHT = 20;
protected final int PADDING = 30;
protected final color TEXT_COLOR = color(150);
//Array list that stores the message in lines
protected ArrayList _lines;
//Constructor
public MenuText(String message)
{
//Assign properties
_height = HEIGHT;
//Current character being accessed
int pos = 0;
//Set the inital hight to 0
_height = 0;
//Create a ArrayList of length 1
_lines = new ArrayList(1);
//While there are characters left
while(pos < message.length())
{
//Current line
String line = "";
//While the current line does not extend past the maximum width + padding
for(;textWidth(line) < Menu.MENU_WIDTH - PADDING && pos < message.length(); pos++)
{
//Add the next character
line += message.charAt(pos);
}
//Add the new line
_lines.add(line);
//Increment height
_height += HEIGHT;
}
//Empty string
if(_height == 0)
{
//Add a blank space
_lines.add("");
_height = HEIGHT;
}
}
//Draw the element
public void draw(int x, int y, int width)
{
//Set font
fill(TEXT_COLOR);
textFont(g_font, 20);
textAlign(CENTER);
//Loop through lines
for(int i = 0; i < _lines.size(); i++)
{
//Get the ypos of the text
int ypos = (int)(y - (height() / 2) + HEIGHT*(2f/3f) + ((HEIGHT)*i));
//Write the current line
text((String)_lines.get(i), x, ypos);
}
}
}
/*
* Playlist - Class respresenting a playlist of elements gained from an XML file
*/
class Playlist
{
//Element Count
protected int _elements = -1;
//Current element
protected int _current = -1;
//XMLElement property
protected XMLElement _xml;
//XML Url
protected String _url = "";
//Error message
protected String _message = null;
//Errornous elements count
protected int _errornousElements = 0;
//Constructor
public Playlist(PApplet xmlParent, String xml)
{
//Try to create the xml file and grab the element count
try
{
_xml = new XMLElement(xmlParent, xml);
_elements = _xml.getChildCount();
}
//If that could not be done
catch (Exception e)
{
//Set element count to -1 and apply a message
_elements = -1;
_message = "XML file could not be loaded";
}
//If the xml file was empty
if(size() == 0)
{
//Assign error message
_message = "No elements could be found in the\nxml playlist";
}
//Assign url variable
_url = xml;
}
//Method to return if an error occurred
public boolean fatalError()
{
return _message != null;
}
//Method to return the error message
public String message()
{
return _message;
}
//Method to return the amount of elements
public int size()
{
return _elements;
}
//Method to return the url of the xml playlist
public String url()
{
return _url;
}
//Method to return the next playlist item's url
public String next()
{
if(fatalError()) return null; //If an error has occurred do not attempt to aquire an element
//Increment current item and make sure it is not out of bounds
_current = (_current + 1) % size();
//Grab url
String url;
//If the url attribute never existed
if((url = loadUrl()).equals("-1"))
{
_errornousElements++; //Increase the errornous element count
if(_errornousElements < size()) _current = (_current + 1) % size(); //Attempt to load next element
//Otherwise return -1 and set the error message
_message = "No valid elements are present within\nthe xml playlist";
}
//Return the url
return url;
}
//Method to load the url from the xml file
protected String loadUrl()
{
//Get the child element
XMLElement o = _xml.getChild(_current);
//Return the url attribute and default to -1 if it cannot be found
return o.getStringAttribute("url", "-1");
}
//Method to return the previous item's url
public String prev()
{
if(fatalError()) return null; //If an error has occurred do not attempt to aquire an element
//Decrement current and make sure it is not out of bounds
_current = (_current == 0 ? size() - 1 : _current - 1);
//Grab url
String url;
//If the url attribute never existed
if((url = loadUrl()).equals("-1"))
{
_errornousElements++; //Increase the errornous element count
if(_errornousElements < size()) _current = (_current == 0 ? size() - 1 : _current - 1); //Attempt to load previous element
//Otherwise return -1 and set the error message
_message = "No valid elements are present within the xml playlist";
}
return url;
}
//Method to get the url of the specified element
public String get(int count)
{
//Assign current to the specified index - 1 and make sure it is not out of bounds
_current = (count - 1) % size();
while(_current < 0) _current += size(); //Add to current until it is positive
return next(); //Return the next element
}
//Method to return a formatted string of the current element out of total elements
public String positionMessage()
{
//Strings to store current and total
String c = Integer.toString(_current + 1);
String t = Integer.toString(_elements);
//Make sure t is always at least 2 characters long
t = _elements < 10 ? "0" + t : t;
//Make sure current is the same length as elements
while(c.length() < t.length())
c = "0" + c; //Prepend 0
return c + "/" + t;
}
}
/*
* Pulsar Visualization - Utilizes the CircleWave alongside a linked stack to created a pulsing wave animation
*/
class Pulsar extends Visualization
{
//Properties
protected float _multiplier = 50;
protected float _x, _y, _width, _height;
//Root pulse
protected Pulse _pulse = null;
//Colors
protected int[][] _colors;
//Color count
protected int _count = 0;
//Constructor
public Pulsar(float x, float y, float w, float h, float multiplier, int[][] colors)
{
//Assign properties
_x = x;
_y = y;
_width = w;
_height = h;
_multiplier = multiplier;
_colors = colors;
}
//Draw method
void draw(float[] left, float[] right, float[] mix, float left_level, float right_level, float mix_level)
{
//Create a new pulse
Pulse p = new Pulse(_x, _y, _width / 4, _height / 4, _multiplier, mix, _colors[_count][0], _colors[_count][1], _colors[_count][2]);
//Set the parent of the new pulse to the current stack top
p.parent = _pulse;
//Set the stack top to the new pulse
_pulse = p;
//Draw the top pulse - and it's parents
_pulse.draw();
//Increment color count and ensure it does not go out of bounds
_count = (_count + 1) % _colors.length;
}
//Reset method
void reset()
{
//Reset the stack with a blank pulse
float[] mix = new float[BUFFER_SIZE];
_pulse = new Pulse(_x, _y, _width / 4, _height / 4, _multiplier, mix, 0, 0, 0);
}
}
/*
* Pulse extends CircleWave to create a linked list for the pulsar animation
*/
class Pulse extends CircleWave
{
//Fade speed
protected final int FADE = 25;
//Expand speed
protected final int EXPAND = 35;
//Parent
public Pulse parent = null;
//Color values
protected int _red = 255;
protected int _blue = 255;
protected int _green = 255;
protected int _alpha = 255;
//Wave data
protected float[] _wave;
//Constructor
public Pulse(float x, float y, float w, float h, float multiplier, float[] wave, int r, int g, int b)
{
//Call the parent construct with no color
super(x, y, w, h, multiplier, color(0));
//Set properties
_wave = wave;
_red = r;
_green = g;
_blue = b;
//Assign step
_step = 8; //Much bigger than circle wave normally, do to recursive functionality and more than 3 waves in the animation at any frame
}
//Method to return current alpha
public int alpha()
{
return _alpha;
}
//Draw method
void draw()
{
//Set the forground color
_foreground = color(_red, _green, _blue, _alpha);
//If the pulse has a parent
if(parent != null) {
//Draw the parent
parent.draw();
//If the parent is now invisible, delete it
if(parent.alpha() <= 0)
parent = null;
}
//Draw the wave form
super.draw(_wave);
//Increase width and height. And decrease alpha
_width += EXPAND;
_height += EXPAND;
_alpha -= FADE;
}
}
/*
* SelectableMenuItem - Abstract class which represents selectable items in a menu
*/
abstract class SelectableMenuItem extends MenuItem
{
//Selected attribute
protected boolean _selected = false;
//Select and deselect methods - cannot be overridden
public final void select()
{
_selected = true;
}
public final void deselect()
{
_selected = false;
}
//updateFromMouse method which all items must implement
abstract public boolean updateFromMouse(int x, int y, int mouseX, int width, int mouseY);
}
/*
* Torrent Visualization - Uses particles alongside a linked list to create an explosion of particles based on the sound wave
*/
class Torrent extends Visualization
{
//Root element
protected TorrentPulse _root = null;
//Attributes for color, size and position
int[][] _colors;
int _x, _y, _width, _height;
//Constructor
public Torrent(int x, int y, int w, int h, int[][] c)
{
//Assign properties
_x = x;
_y = y;
_width = w;
_height = h;
_colors = c;
}
//Draw method
public void draw(float[] left, float[] right, float[] mix, float left_level, float right_level, float mix_level)
{
//Create a new torrent pulse element
TorrentPulse n = new TorrentPulse(mix, left_level, right_level, _x, _y, _width, _height, _colors);
//Set the parent of the new pulse to the current top item
n.parent = _root;
//Set the root item to the new pulse
_root = n;
//Enable smoothing
smooth();
//Draw the elements
_root.draw();
//Disable smoothing
noSmooth();
}
//Reset method
public void reset()
{
//Reset the stack to one empty pulse
float[] mix = new float[BUFFER_SIZE];
_root = new TorrentPulse(mix, 0, 0, _x, _y, _width, _height, _colors);
}
}
/*
* TorrentPulse - Represents an item in the Torrent linked list and generates a set of partcles in the shape of the wave around a circle
*/
class TorrentPulse
{
//Constants for animation
private final int STEP = 10, SPEED = 10, ALPHA_STEP = 35, GRAVITY = 6, SIZE = 20;
//Attributes for each particle
protected float[] _particles_x, _particles_y;
protected float[] _speed_x, _speed_y;
protected int[][] _colors;
//Attributes for alpha and size
protected int _alpha = 255;
protected int _size = 1;
//Parent element in list
public TorrentPulse parent = null;
//Constructor
public TorrentPulse(float[] mix, float left, float right, int x, int y, int width, int height, int[][] c)
{
//Create new arrays based on the amount of particles being generated
_particles_x = new float[(int)ceil((float)mix.length/STEP)];
_particles_y = new float[(int)ceil((float)mix.length/STEP)];
_speed_x = new float[(int)ceil((float)mix.length/STEP)];
_speed_y = new float[(int)ceil((float)mix.length/STEP)];
_colors = new int[(int)ceil((float)mix.length/STEP)][3];
//Get the size of the particles based on the left channel
_size = (int)(left * SIZE);
//Counter for the particles
int counter = 0;
//Loop through the data
for(int i = 0; i < mix.length; i += STEP)
{
//Determine the current angle
float angle = TWO_PI * ((float)i / mix.length);
//Determine the starting position for the particle
float sx = x + sin(angle)*(width / 2) * (mix[i] + 0.3);
float sy = y + cos(angle)*(height / 2) * (mix[i] + 0.3);
//Assign particle location
_particles_x[counter] = sx;
_particles_y[counter] = sy;
//Assign speed of the particles based on the right channel
_speed_x[counter] = sin(angle)*((SPEED) * right);
_speed_y[counter] = cos(angle)*((SPEED) * right);
//Determine the color of the particles based on the wave
int index = (int)(round(c.length * abs(mix[i]) + 0.3)) % c.length;
_colors[counter] = c[index];
counter++;
}
}
//Method to return the alpha
public int alpha()
{
return _alpha;
}
//Draw method
public void draw()
{
//Disable stroke and set ellipse mode
noStroke();
ellipseMode(CENTER);
//If the pulse has a parent
if(parent != null)
{
//Draw the parent
parent.draw();
//If the parent is invisible, delete it
if(parent.alpha() <= 0) parent = null;
}
//Loop through the particles
for(int i = 0; i < _particles_x.length; i++)
{
//Set the fill color to the particles color
fill(_colors[i][0], _colors[i][1], _colors[i][2], _alpha);
//Draw rhe particle
ellipse(_particles_x[i], _particles_y[i], _size + 1, _size + 1);
//Add to the particle location based on the speed
_particles_x[i] += _speed_x[i];
_particles_y[i] += _speed_y[i];
//Add gravity's acceleration to the y_speed
_speed_y[i] += GRAVITY;
}
//Decrement alpha
_alpha -= ALPHA_STEP;
}
}
/*
* TriCircleWave Visualization - Visualization created by one CircleWave representing the mix channel. With two internal CircleWaves representing right and left, and optionally rotating
*/
class TriCircleWave extends Visualization
{
//Waves
private CircleWave _left, _right, _mix;
//Properties for current rotation angle and spin value
protected int _spin = 0;
protected float _angle = 0;
//Variables representing size of inner circles and position of the visualization
protected int _small_width, _small_height, _x, _y;
//Constructor
public TriCircleWave(int x, int y, int w, int h, int d, color leftColor, color rightColor, color mixColor, int spin)
{
//Create the mix wave
_mix = new CircleWave(x, y, w, h, d, mixColor);
//Assign properties
_small_width = w / 3;
_small_height = h / 3;
_spin = spin;
_x = x;
_y = y;
//Create left and right waves
_left = new CircleWave(x - _small_width / 1.5, y, _small_width, _small_height, d / 3, leftColor);
_right = new CircleWave(x + _small_width / 1.5, y, _small_width, _small_height, d / 3, rightColor);
}
//Draw method
public void draw(float[] left, float[] right, float mix[], float left_level, float right_level, float mix_level)
{
//If spinning is enabled
if(_spin != 0)
{
//Increase angle
_angle += (PI/180 * _spin);
//Set new position of the waves
_left.setPos (_x - (cos(_angle) * (_small_width / 1.5)), _y - (sin(_angle) * (_small_height / 1.5)));
_right.setPos(_x + (cos(_angle) * (_small_width / 1.5)), _y + (sin(_angle) * (_small_height / 1.5)));
}
//Draw the 3 waves
_mix.draw(mix);
_left.draw(left);
_right.draw(right);
}
}
//Abstract visualization class which all visualizations must inherit to be used within the ImaViz application
abstract class Visualization
{
//Draw method which is called every frame, the left, right and mix channels are passed in, including the average levels of all 3. Not all values need to be used
abstract public void draw(float[] left, float[] right, float[] mix, float left_level, float right_level, float mix_level);
//Is called when visualization is changed - useful for pulsar or any other linked-stack type visualization
public void reset() {};
}
/*
* Wave - Used to display a classic scope type wave
*/
class Wave
{
//The increment of the visualization, the smaller this number is, the more detailed the wave will be
protected final int STEP = 2;
//Stroke weight
protected final int STROKE_WEIGHT = 1;
//Properties for size and position
protected int _width, _height, _x, _y;
//Property for color
color _color;
//Constructor
public Wave(int x, int y, int w, int h, color c)
{
//Assign properties
_x = x;
_y = y;
_width = w;
_height = h;
_color = c;
}
//Draw method
void draw(float[] wave)
{
//Loop through the data
for(int i = 0; i < wave.length; i += STEP)
{
//Get the start and end position of the current line of the wave
float startX = _x + (_width * ((float)(i - wave.length / 2) / wave.length));
float endX = _x + (_width* ((float)(i + STEP - wave.length / 2) / wave.length));
float startY = _y + (_height / 2 * wave[i]);
float endY = _y + (_height / 2 * wave[(i+STEP)%wave.length]);
//Set the stroke color and weight
stroke(_color);
strokeWeight(STROKE_WEIGHT);
//Draw the line
line(startX, startY, endX, endY);
}
}
}
This is my coursework submissions for my Animation and Multimedia unit at Keele. It is a music visualizer which has 9 visualizations. Unfortunately because this is an applet, it cannot access external files. But I have a separate version (linked in the main menu) which can load external playlists for music and the image slideshow. I think my experience as an application developer leaks through into this project a little.
I have received a grade for this project now, and I was awarded 78%. Thank you to anyone who viewed my submission.
Judith Stockton