Return to Snippet

Revision: 45469
at May 1, 2011 10:49 by MattRix


Initial Code
[SWF (backgroundColor=0, width=800,height=600, frameRate=60)]


//////////////////////////////////////////
//ROCK MUSIC featuring THE ROLLING STONE//
//////////////////////////////////////////
//By Matt Rix							//
//////////////////////////////////////////
//Hold down your mouse on the left and	//
//right sides of the screen to move 	//
//the stone. The closer you are to the  //
//edge of the screen, the more force 	//
//you'll have. Let go of the mouse to 	//
//let gravity take over. 				//
//										//
//None of the variables are typed, so	//
//I ran into some huge speed problems	//
//when doing the audio stuff on 		//
//thousands of samples. Because of that //
//this toy requires a pretty fast 		//
//computer to run smoothly.				//
//////////////////////////////////////////	


//freebies
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.SHOW_ALL;

			
//1: create a config object to store all of our config options as well as creating a handy "scope" reference. 
var scope:* = (this.config = {x:400, y:300, width:800, height:600, sceneWidth:10000, segmentWidth:8, terrainFlatness:0.01, musicTempo:150, musicKey:440, acceleration:.4, gravity:.4, maxSpeed:15, lastX:0, lastY:0, scope:this}).scope;
//2: create the stone texture			
(scope.wheelBMD = new BitmapData(100,100,false,0)).perlinNoise(20,20,2,123,true,true,7,true);
//3: create the wheel
MovieClip(MovieClip(addChild(scope.level = new MovieClip())).addChild(scope.wheel = new MovieClip())).graphics.beginBitmapFill(scope.wheelBMD,null,true,false);
//4: draw the wheel and init the terrain drawing vars
scope.points = [new Point((scope.speedX = int(scope.wheel.graphics.drawCircle(int(scope.level.graphics.lineStyle(3,0x00FF00,1)),int(scope.level.graphics.moveTo(0,300)),30))), (scope.mountY = (scope.wheel.x = scope.config.height/2)+(scope.mountSpeed = (scope.lineIndex = 0))))];
//5: build an array of terrain points
while((scope.points = scope.points.concat([new Point(scope.points.length*scope.config.segmentWidth, scope.mountY = (scope.config.height/2+(scope.mountY-scope.config.height/2)*.9) + (scope.mountSpeed = (1-scope.config.terrainFlatness)*(scope.mountSpeed + -3 + Math.random()*6)))])).length < Math.ceil(scope.config.sceneWidth/scope.config.segmentWidth));
//6: draw the terrain points
while((scope.lineIndex = scope.lineIndex + 1 + int(scope.level.graphics.lineTo(scope.points[scope.lineIndex].x, scope.points[scope.lineIndex].y))) < scope.points.length);

//7: listen for enter frames
addEventListener(Event.ENTER_FRAME, function(e:Event):void
{
	//8: move the wheel(the rotation value is the same as the x value)
	(scope.wheel.x = Math.max(0, Math.min(scope.config.sceneWidth-1, (scope.wheel.x + .5*((scope.speedX = Math.max(-scope.config.maxSpeed, Math.min(scope.config.maxSpeed, scope.speedX*.99 + int(scope.isMouseDown)*(scope.config.acceleration*2*(scope.mouseX-(scope.level.x + scope.wheel.x))/scope.config.width)))))))));
	//9: interpolate the terrain points to figure out what y-value the ground is at, and put the wheel on the ground
	scope.wheel.y = -30 + (scope.baseY = scope.points[Math.max(0, Math.min(Math.floor(scope.config.sceneWidth/scope.config.segmentWidth)-1, Math.floor(scope.wheel.x/scope.config.segmentWidth)))].y)+  (scope.deltaY = (scope.points[Math.max(0, Math.min(Math.floor(scope.config.sceneWidth/scope.config.segmentWidth)-1, Math.floor(scope.wheel.x/scope.config.segmentWidth)+1))].y - scope.points[Math.max(0, Math.min(200000, Math.floor(scope.wheel.x/scope.config.segmentWidth)))].y))  *  ((scope.wheel.x%scope.config.segmentWidth)/scope.config.segmentWidth);
	//10: figure out the ground's angle and apply gravity
	scope.speedX += Math.sin(Math.atan2(scope.deltaY,scope.config.segmentWidth))*scope.config.gravity;
	//11: rotate the wheel based on our direction and speed
	scope.wheel.rotation += scope.speedX/Math.abs(scope.speedX) * Math.sqrt((scope.config.lastX - scope.wheel.x) * (scope.config.lastX - scope.wheel.x) + (scope.config.lastY - scope.wheel.y) * (scope.config.lastY - scope.wheel.y)) + (scope.config.lastX = scope.wheel.x)*0 + (scope.config.lastY = scope.wheel.y)*0;
	//12: move and ease the "camera" x
	scope.level.x += ((scope.config.width/2 - scope.wheel.x) - scope.level.x)/30;
	//13: move and ease the "camera" y
	scope.level.y += ((scope.config.height/2 - scope.wheel.y) - scope.level.y)/30;
});

//14: listen for mouse downs
stage.addEventListener(MouseEvent.MOUSE_DOWN, function(e:MouseEvent):void { scope.isMouseDown = true});
//15: listen for mouse ups
stage.addEventListener(MouseEvent.MOUSE_UP, function(e:MouseEvent):void { scope.isMouseDown = false});

//16: create the music config. This only counts as a single line, because if I took out the line breaks, it would be :)
//it creates a bunch of "instrument" settings that define the 3 instruments that play. Fiddling around with these variables leads to tons of fun stuff
scope.music = 
{
	numSamples:2048,
	scale:[2,2,3,2,3], //major pentatonic scale
	sample:0,
	instrument:
	[
		/*MELODY*/{freq:0, position:0, noteLength:Math.round(1/4 *44100*60/scope.config.musicTempo*4), beatOffset:0, phase:0, volume:.4, envVolume:0, percent:0, sample:0, currentNote:0, mouseRange:30, numNotes: 21, startNote:20, baseFreq:scope.config.musicKey/4, freqs:[]},
		/*BASS*/{freq:0, position:0, noteLength:Math.round(8/4 *44100*60/scope.config.musicTempo*4), beatOffset:0, phase:0, volume:.3, envVolume:0, percent:0, sample:0, currentNote:0, mouseRange:100, numNotes: 12, startNote:7, baseFreq:scope.config.musicKey/8, freqs:[]},
		/*TINKLES*/{freq:0, position:0, noteLength:Math.round(3/8 *44100*60/scope.config.musicTempo*4), beatOffset:0, phase:0, volume:.2, envVolume:0, percent:0, sample:0, currentNote:0, mouseRange:1, numNotes: 6, startNote:5, baseFreq:scope.config.musicKey/1, freqs:[]},
	]
};

//17: generate an array of all the needed frequencies in a scale
for each(var instrument:Object in scope.music.instrument) while((instrument.freqs = instrument.freqs.concat([(instrument.baseFreq = instrument.baseFreq * Math.pow(1.05946309435929, scope.music.scale[instrument.freqs.length%scope.music.scale.length]))])).length < instrument.numNotes);

//18: create a nwe dynamic sound and start listening to it
(scope.dynamicSound = new Sound()).addEventListener(SampleDataEvent.SAMPLE_DATA, function (e:SampleDataEvent):void
{
	//19: loop through the samples
	for(var i:int = 0; i<scope.music.numSamples; i++)
	{
		//20: loop through the instrument information 
		for each(var instrument:Object in scope.music.instrument)
		{
			//21: if we're on a beat, get a new frequency for the note based on the ball's y position
			if(((instrument.position = instrument.position+1) + instrument.beatOffset)%instrument.noteLength == 0) instrument.freq = Math.min(scope.config.musicKey*4, instrument.freqs[((instrument.currentNote = instrument.startNote-Math.round(scope.wheel.y/instrument.mouseRange))+instrument.freqs.length*100000)%instrument.freqs.length]);
			
			//I used to have a ton of sweet volume envelope stuff in here(attack, hold, release), but the untyped references made everything run so slow that I couldn't get more than one instrument at a time without it conking out
			
			//22: add this instruments's samples to the full sample
			scope.music.sample += ((Math.sin((instrument.phase = instrument.phase + instrument.freq/44100)*Math.PI*2)) * instrument.volume * 0.2 * (0.7+0.3*Math.abs(scope.speedX)/15));  
		}
		//23: write the samples to the audio buffer. I was trying to come up with a way to combine the bytes of these two floats and write them as a double, but it's tricky, and this is much simpler
		e.data.writeFloat(scope.music.sample + (scope.music.sample = int(e.data.writeFloat(scope.music.sample))));
	}
	
});

//24: add this instruments's samples to the full sample
(scope.dynamicSoundChannel = scope.dynamicSound.play());
	

//25: I left the 25th line blank in case I accidentally broke a rule somewhere. I know I got creative with chaining, but I made sure to never do any cheap recursive/infinite stuff.

Initial URL


Initial Description
This was create for Keith Peters' now defunct 25lines AS3 competition. This entry fit within all the rules of the competition.

To try it yourself, simply paste this code on the first frame of an empty FLA. 

To view it live, check it out here: [http://struct.ca/share/rock](http://struct.ca/share/rock)

Initial Title
RockMusic 25 lines generative music entry

Initial Tags


Initial Language
ActionScript 3