Create a AS3 MP3 Player with papervision3d spectrum display

by Rafael Nuenlist 24

As we’ve already promised you, here’s the tutorial about how to create a 3d spectrum display from a sound file with papervision3d and flash.
On this tutorial, we’ll focus on the 3d part. If you’re not comfortable with the SoundMixer.computeSpectrum method then you should read the previous tutorial.
So, let’s start with the fun.

Requirements

Adobe Flash CS3

Try / Buy

Source Files

Download

Getting Started

For this demo application, we’ve used the latest version of papervision3d with the code name GreatWhite. First of all, we need to include the papervision3d stuff.

import org.papervision3d.cameras.Camera3D;
import org.papervision3d.scenes.Scene3D;
import org.papervision3d.view.Viewport3D;
import org.papervision3d.render.BasicRenderEngine;
import org.papervision3d.objects.primitives.Cube;
import org.papervision3d.materials.ColorMaterial;
import org.papervision3d.materials.utils.MaterialsList;

These are all the classes we need for our application. The Camera3D class enables you to focus on a 3d object and zoom to it. Scene3D is a container that holds all 3d objects. The Viewport will be added to the main stage and is like a video screen that shows the result. Another important class is the BasicRenderEngine which renders all the objects from the scene with the current camera settings and outputs the result to the viewport. Papervision comes with different types of objects, but for our example we’ll only need the cube. And finally there are the materials which can be set to the objects. This is how papervision basically works.

Then we to set some constants for the sound channel

const CHANNELS_PER_DIRECTION:int= 256;
const CUBES_PER_CHANNEL:int		= 8;
const CHANNEL_STEPS:int			= Math.floor(CHANNELS_PER_DIRECTION / CUBES_PER_CHANNEL);

Next thing to do is to set the papervision variables

var viewport:Viewport3D;
var scene:Scene3D;
var camera:Camera3D;
var cube:Cube;
var renderer:BasicRenderEngine;

To keep a reference of each cube, we need to create 2 arrays. For each sound channel (left, right)

var cubesLeftChannel:Array		= new Array();
var cubesRightChannel:Array		= new Array();

At the end, we want to swing our camera from left to right and vice versa. To know in which direction we’re currently moving, we need to set a flag:

var bolAnimationForward:Boolean 	= true;

The next part is the same as in the previous tutorial. We create a new sound object and a url request for requesting the mp3 file. SndBytes will hold the current spectrum data.

var sndObject:Sound			= new Sound();
var reqObject:URLRequest 		= new URLRequest("so-deep.ram2000.mp3");
var sndBytes:ByteArray			= new ByteArray();

So, the next step will be the initializing of our 3d stuff, then loading the soundfile and finally creating the render loop. First of all, we create a new viewport and add it to the stage. The third parameter tells the viewport to use the whole width and height of the swf file for displaying the 3d objects.

viewport = new Viewport3D(0, 0, true);
addChild(viewport);

Then we create a new basic render engine and a scene which holds the 3d objects

renderer = new BasicRenderEngine();
scene = new Scene3D();

Our cubes will need a material. We create a blue one for cubes that are displaying the left channel and a green material for the other ones. You can easily change the colors for each side using hex values.

var blueMaterial:MaterialsList = new MaterialsList(
	{
		front:  new ColorMaterial(0x0066FF),
		back:   new ColorMaterial(0x0066FF),
		right:  new ColorMaterial(0x0046B0),
		left:   new ColorMaterial(0x0046B0),
		top:    new ColorMaterial(0x1171FF),
		bottom: new ColorMaterial(0x1171FF)
	}
);

var greenMaterial:MaterialsList = new MaterialsList(
	{
		front:  new ColorMaterial(0x00CC00),
		back:   new ColorMaterial(0x00CC00),
		right:  new ColorMaterial(0x009F00),
		left:   new ColorMaterial(0x009F00),
		top:    new ColorMaterial(0x00CC00),
		bottom: new ColorMaterial(0x00CC00)
	}
);

Now we need to create the cubes for the left channel, position them correctly on the x-axis, keep a reference in the array and add them to the scene

for(var i = 0; i < CUBES_PER_CHANNEL; i++) {
	cube = new Cube(blueMaterial, 12, 50, 400);
	cube.x = i * 50;
	cubesLeftChannel.push(cube);
	scene.addChild(cube);
}

The same procedure for the cubes standing for the right channel but with the green material and we place them between the green ones.

for(i = 0; i < CUBES_PER_CHANNEL; i++) {
	cube = new Cube(greenMaterial, 12, 50);
	cube.x = (i * 50) + 25;
	cubesRightChannel.push(cube);
	scene.addChild(cube);
}

Finally we create a new camera, set the target to the cube in the middle, adjust the zoom and move the start position a bit to the upper left

camera = new Camera3D();
camera.zoom = 11;
camera.target = cubesRightChannel[CUBES_PER_CHANNEL / 2 - 1] as Cube;
camera.x -= 400;
camera.y += 300;

Now we’re finished with creating all the necessary 3d stuff. Next thing to do is loading a sample mp3 file which is quite easy. We just load the url request we’ve created bevore and play the file.

sndObject.load(reqObject);
sndObject.play();

The only thing left to do is the render loop. This loop will be executed on enter frame, in our case 60 times per second. First, we compute the spectrum and put the result in the defined byte array.

SoundMixer.computeSpectrum(sndBytes);

Then we set the right position for the byte array pointer, read the float value and assign it to the current cube scaleY value. Like this, the cubes will be resized according to the spectrum. After the end of the first loop, we repeat the procedure for the right channel cubes

for(var i = 0; i < CUBES_PER_CHANNEL; i++) {
	var myCube:Cube = cubesLeftChannel[i] as Cube;
	sndBytes.position = CHANNEL_STEPS * 4 * i;
	myCube.scaleY =  sndBytes.readFloat();
}
for(i = 0; i < CUBES_PER_CHANNEL; i++) {
	myCube = cubesRightChannel[i] as Cube;
	sndBytes.position = 1024 + (CHANNEL_STEPS * 4 * i);
	myCube.scaleY = sndBytes.readFloat();
}

To swing our camera a bit, we increase and decrease it’s x value

if(bolAnimationForward) {
	camera.x += 4;
	if(camera.x > 800)
		bolAnimationForward = false;
} else {
	camera.x -= 4;
	if(camera.x < -400)
		bolAnimationForward = true;
}

And now we need to tell the render engine to render the current scene, camera and viewport

renderer.renderScene(scene, camera, viewport);

That was already the whole trick. You can also try rendering other materials and different objects. These were just the basics about papervision3d. We hope you enjoyed reading this tutorial. Any feedbacks and questions are welcome.

In the final you will get this:


Full code with comments

// Import Papervision3D
import org.papervision3d.cameras.Camera3D;
import org.papervision3d.scenes.Scene3D;
import org.papervision3d.view.Viewport3D;
import org.papervision3d.render.BasicRenderEngine;
import org.papervision3d.objects.primitives.Cube;
import org.papervision3d.materials.ColorMaterial;
import org.papervision3d.materials.utils.MaterialsList;

// constants
const CHANNELS_PER_DIRECTION:int= 256;
const CUBES_PER_CHANNEL:int		= 8;
const CHANNEL_STEPS:int			= Math.floor(CHANNELS_PER_DIRECTION / CUBES_PER_CHANNEL);

// papervision vars
var viewport:Viewport3D;
var scene:Scene3D;
var camera:Camera3D;
var cube:Cube;
var renderer:BasicRenderEngine;

// arrays holding references to cubes for each channel
var cubesLeftChannel:Array		= new Array();
var cubesRightChannel:Array		= new Array();
// animation flag
var bolAnimationForward:Boolean = true;

// sound vars
var sndObject:Sound				= new Sound();
var reqObject:URLRequest 		= new URLRequest("so-deep.ram2000.mp3");
var sndBytes:ByteArray			= new ByteArray();

// INIT
function init():void {
	init3D();
	initSound();
	// add event listener for render loop
	addEventListener(Event.ENTER_FRAME, renderLoop);
}

// init3d
function init3D():void {
	// Create viewport
	viewport = new Viewport3D(0, 0, true);
	// add viewport to stage
	addChild(viewport);

	// create basic render engine
	renderer = new BasicRenderEngine();

	// Create scene
	scene = new Scene3D();

	// blue cube material
	var blueMaterial:MaterialsList = new MaterialsList(
		{
			front:  new ColorMaterial(0x0066FF),
			back:   new ColorMaterial(0x0066FF),
			right:  new ColorMaterial(0x0046B0),
			left:   new ColorMaterial(0x0046B0),
			top:    new ColorMaterial(0x1171FF),
			bottom: new ColorMaterial(0x1171FF)
		}
	);

	// green cube material
	var greenMaterial:MaterialsList = new MaterialsList(
		{
			front:  new ColorMaterial(0x00CC00),
			back:   new ColorMaterial(0x00CC00),
			right:  new ColorMaterial(0x009F00),
			left:   new ColorMaterial(0x009F00),
			top:    new ColorMaterial(0x00CC00),
			bottom: new ColorMaterial(0x00CC00)
		}
	);

	// create cubes for left channel
	for(var i = 0; i < CUBES_PER_CHANNEL; i++) {
		cube = new Cube(blueMaterial, 12, 50, 400);
		cube.x = i * 50;
		cubesLeftChannel.push(cube);
		scene.addChild(cube);
	}
	//create cubes for the right channel
	for(i = 0; i < CUBES_PER_CHANNEL; i++) {
		cube = new Cube(greenMaterial, 12, 50);
		cube.x = (i * 50) + 25;
		cubesRightChannel.push(cube);
		scene.addChild(cube);
	}

	// create camera
	camera = new Camera3D();
	// set camera zoom
	camera.zoom = 11;
	// set cube in the center as camera target
	camera.target = cubesRightChannel[CUBES_PER_CHANNEL / 2 - 1] as Cube;
	// camera start position
	camera.x -= 400;
	camera.y += 300;
}

function initSound():void {
	// load mp3 file and play it
	sndObject.load(reqObject);
	sndObject.play();
}

// render loop function
function renderLoop(e:Event):void
{
	// compute spectrum and put result in byte array
	SoundMixer.computeSpectrum(sndBytes);

	//
	// LEFT CHANNEL CUBES
	for(var i = 0; i < CUBES_PER_CHANNEL; i++) {
		var myCube:Cube = cubesLeftChannel[i] as Cube;
		// get the right positions for the byte array
		sndBytes.position = CHANNEL_STEPS * 4 * i;
		// resize current cube
		myCube.scaleY =  sndBytes.readFloat();
	}
	//
	// RIGHT CHANNEL CUBES
	for(i = 0; i < CUBES_PER_CHANNEL; i++) {
		myCube = cubesRightChannel[i] as Cube;
		// get the right positions for the byte array
		sndBytes.position = 1024 + (CHANNEL_STEPS * 4 * i);
		// resize current cube
		myCube.scaleY = sndBytes.readFloat();
	}

	// swing camera
	if(bolAnimationForward) {
		camera.x += 4;
		if(camera.x > 800)
			bolAnimationForward = false;
	} else {
		camera.x -= 4;
		if(camera.x < -400)
			bolAnimationForward = true;
	}

	// render current scene
	renderer.renderScene(scene, camera, viewport);
}

init();

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>