• fullscreen
  • Blitz.pde
  • Burst.pde
  • Chaos.pde
  • ChaosPulse.pde
  • CircleWave.pde
  • Classic.pde
  • Ecliptic.pde
  • FadingText.pde
  • Ima.pde
  • ImaViz.pde
  • Menu.pde
  • MenuButton.pde
  • MenuItem.pde
  • MenuText.pde
  • Playlist.pde
  • Pulsar.pde
  • Pulse.pde
  • SelectableMenuItem.pde
  • Torrent.pde
  • TorrentPulse.pde
  • TriCircleWave.pde
  • Visualization.pde
  • Wave.pde
  • /*
     * 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);			
    		}
    	}
    	
    }
    

    code

    tweaks (0)

    about this sketch

    This sketch is running as Java applet, exported from Processing.

    license

    advertisement

    Ashley Blurton

    ImaViz - Sound Visualizer

    Add to Faves Me Likey@! 43
    You must login/register to add this sketch to your favorites.

    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.

    Felix Woitzel
    17 Jun 2010
    Oh, I have made it crash. It freezes when pressing Esc. Great stuff anyway, have a look at my portfolio ;)
    Ashley Blurton
    17 Jun 2010
    I think the Esc thing is a processing or java thing. Since I just did the same thing to your own sketches ;)
    Felix Woitzel
    17 Jun 2010
    Oh, i have never noticed that before. Kudos, anyway!
    Ashley Blurton
    17 Jun 2010
    Me either, but I know I never processed the Escape key in the Sketch, since I was developing for an application and Esc closed the entire app window. SO I decided to try it out on one of yours. Liking the smoke dynamics!
    Guigui plus+
    1 Jul 2010
    wow! 8^)
    Me
    5 Jul 2010
    Hi Ashely, Would you be interested in doing some paid work with your music visualizer for a project?

    Thanks
    Me
    5 Jul 2010
    Hi Ashely, Would you be interested in doing some paid work with your music visualizer for a project?

    Thanks
    Gerard Geer
    5 Jul 2010
    I froze it too with the help menu open by scrolling through the visulizations once then scrolling to the rainbow circle one with help menu open, then trying to go back to the main menu.
    Me
    6 Jul 2010
    Anyone have the XML files to download or email?
    Judith Stockton
    13 Jan 2012
    Wow!!! Great idea, and visuals !!!!!!!
    Esc works as exit in Processing. How to change this is described here:

    http://www.processing.org/discourse/beta/num_1276201899.html
    You need to login/register to comment.