I've been trying to implement a FRIM system for my game for the last couple of days.I did some research and came upon this article - it seemed simple enough to implement so I got started.
Everything seems to work fine except I get some temporal aliasing (moving bodies seem to jump ahead a bit) - this happens when more Box2D steps are processed....I think.
private const FIXED_TIMESTEP:Number = 1 / 60;
private const velocityIterations:int = 8;
private const positionIterations:int = 3;
private var fixedTimestepAccumulator:Number = 0;
private var fixedTimestepAccumulatorRatio:Number = 0;
public function Step(dt:Number):void
{
//dt - time between frames - I'm passing the e.passedTime - from the enter frame event; using Starling
fixedTimestepAccumulator += dt;
var nSteps:uint = Math.floor(fixedTimestepAccumulator / FIXED_TIMESTEP);
if (nSteps > 0)
{
fixedTimestepAccumulator = fixedTimestepAccumulator - nSteps * FIXED_TIMESTEP;
}
fixedTimestepAccumulatorRatio = fixedTimestepAccumulator / FIXED_TIMESTEP;
var nStepsClamped:int = Math.min(nSteps, MAX_STEPS);
for (var i:int = 0; i < nStepsClamped; ++i)
{
resetSmoothStates();
singleStep(FIXED_TIMESTEP);
}
world.ClearForces();
smoothStates();
}
private function resetSmoothStates():void
{
for (var bb:b2Body = world.GetBodyList(); bb; bb = bb.GetNext())
{
if (bb.GetUserData() is MyUserData && bb.GetType() != b2Body.b2_staticBody )
{
//each of my bodies have a reference to their sprite (actor) in userData
var _userdata:MyUserData=bb.GetUserData();
_userdata.x = _userdata.bodyPreviousX = bb.GetPosition().x * RATIO;
_userdata.y= _userdata.bodyPreviousY = - bb.GetPosition().y* RATIO;
_userdata.rotation = _userdata.bodypreviousRotation= _userdata.bodypreviousRotation = - bb.GetAngle();
}
}
}
private function smoothStates():void
{
var oneMinusRatio:Number = 1.0 - fixedTimestepAccumulatorRatio;
for (var bb:b2Body = world.GetBodyList(); bb; bb = bb.GetNext())
{
if (bb.GetUserData() is MyUserData && bb.GetType() != b2Body.b2_staticBody )
{
var userdata=bb.GetUserData();
userdata.x = (fixedTimestepAccumulatorRatio * bb.GetPosition().x * RATIO + oneMinusRatio * userdata.bodyPreviousX) ;
userdata.y = (- fixedTimestepAccumulatorRatio * bb.GetPosition().y * RATIO + oneMinusRatio * userdata.bodyPreviousY) ;
userdata.rotation = (- fixedTimestepAccumulatorRatio * bb.GetAngle() + oneMinusRatio * userdata.bodypreviousRotation);
}
}
}
private function singleStep(dt:Number):void
{
Input();
world.Step(dt, velocityIterations, positionIterations);
}
What am I doing wrong?
Any help, suggestions would be highly appreciated.
Thanks
I did this once in a game I was working on so that I could lockstep the game to a certain update rate (this was in iOS). This was based on the code in Daley's book (Learning iOS Game Programming), which was based on some other article in the web (I believe, it may be Allen Bishop's). The code looked like this:
void GameManager::UpdateGame()
{
const uint32 MAXIMUM_FRAME_RATE = Constants::DEFAULT_OBJECT_CYCLES_PER_SECOND();
const uint32 MINIMUM_FRAME_RATE = 10;
const uint32 MAXIMUM_CYCLES_PER_FRAME = (MAXIMUM_FRAME_RATE/MINIMUM_FRAME_RATE);
const double UPDATE_INTERVAL = (1.0/MAXIMUM_FRAME_RATE);
static double lastFrameTime = 0.0;
static double cyclesLeftOver = 0.0;
double currentTime;
double updateIterations;
currentTime = CACurrentMediaTime();
updateIterations = ((currentTime - lastFrameTime) + cyclesLeftOver);
if(updateIterations > (MAXIMUM_CYCLES_PER_FRAME*UPDATE_INTERVAL))
{
updateIterations = MAXIMUM_CYCLES_PER_FRAME*UPDATE_INTERVAL;
}
while (updateIterations >= UPDATE_INTERVAL)
{
// DebugLogCPP("Frame Running");
updateIterations -= UPDATE_INTERVAL;
// Set the random seed for this cycle.
RanNumGen::SetSeed(_cycleManager->GetObjectCycle());
// Dispatch messages.
_messageManager->SendMessages();
// Update all entities.
_entityManager->Update();
// Update the physics
_gameWorldManager->Update(Constants::DEFAULT_OBJECT_CYCLE_SECONDS());
// Advance the cycle clock.
_cycleManager->Update();
}
cyclesLeftOver = updateIterations;
lastFrameTime = currentTime;
}
I can't put my finger on the specific item that is wrong in yours. However, this part is suspicious:
var nStepsClamped:int = Math.min(nSteps, MAX_STEPS);
Before this, you updated your accumulator with:
fixedTimestepAccumulator = fixedTimestepAccumulator - nSteps * FIXED_TIMESTEP;
But now the actual number of steps you are going to execute may be different because of the clamping (nStepsClamped). So your time accumulation is different than the number of steps you actually execute.
Was this helpful?
I decided to go with another approach. I'm using filtered delta times for the physics (I know this can cause some problems).
Here's what I'm doing now:
//Play around with this filter value if things don't look right
var filter:Number=0.4;
filtered_dt= time_between_frames * filter + filtered_dt * (1 - filter);
//Poll imputs and apply forces
// I use velocityIterations =6, positionIterations=3
world.Step(filtered_dt, velocityIterations, positionIterations);
//move sprites here
world.ClearForces();
Another thing you have to do is to scale the forces you apply to your bodies using filtered_dt so things don't "explode" when the frame rate changes a lot.
Hope this helps some one else ... it's not the perfect solution but it works for me.
If you have slow moving bodies the interpolation method above should work fine too.
Related
I never really write much actionscript let alone as3.
I've run into a little problem and would like to ask for ideas or help.
I am trying to simulate a LED matrix of 40x40 LEDs, that are supposed to blink at slightly different intervals including fade out.
I use an array for new sprites and write the 40x40 to screen - applying various tweens in the process. Event listeners are supposed to restart the tweens, so they would loop at the various tween intervals set in the 'for' loop.
task complete: this is what i came up with. it's pretty cost intensive with the number of eventlisteners. cpu temp will increase slightly but it works. still interested in a more efficient way of doing similar animation. pls grant me that i'm not really familiar with as3.
mistake seems to have been the call to tween in the onFinish function. should have been e.target.yoyo() instead of the array element.
import fl.transitions.Tween;
import fl.transitions.easing.*;
import fl.transitions.TweenEvent;
import flash.display.Sprite;
var stageWidth: Number = stage.stageWidth;
var n: Number = 40; //matrix width and height
var nsquare: Number = (n * n);
var segment: Number = stageWidth / n; //stage diveded by matrix n
var dot_items: Array = [];
var tweens: Array = [];
var basetime: int = 1; //transition base time ..add half basetime random for various transition speed.
for (var l: int = 0; l < n; l++) {
for (var i: int = 0; i < n; i++) {
var count: Number = (l * n) + i;
dot_items[count] = new Sprite();
dot_items[count].graphics.beginFill(0x141496, 1);
dot_items[count].graphics.drawCircle(segment / 2 + (segment * i), segment / 2 + (segment * l), segment / 2);
dot_items[count].graphics.endFill();
addChild(dot_items[count]);
}
}
for (var it: int = 0; it < nsquare; it++) {
var fadeTime: Number = Math.random() * (basetime / 3) + basetime;
tweens[it] = new Tween(dot_items[it], "alpha", Regular.easeInOut, 1, 0, fadeTime, true);
function onFinish(e: TweenEvent): void {
e.target.yoyo();
}
tweens[it].addEventListener(TweenEvent.MOTION_FINISH, onFinish);
}
I'm currently stuck with my approach below. I'm not entirely sure if using "hitTestObject" method is appropriate in pairing the pieces to their respective place. I was able to at least match the chess piece to their respective location (that's the best I can do and I feel i'm doing it wrong) but I'm now stuck in counting how many pieces are actually in their correct places. e.g. when I move the pawn to a different tile, it will still count as one, I also want to avoid duplicate counting, example, If pawn is already in the correct location, it will just count as 1, and if it was moved, then that count will be removed. Only count the pieces that are in the correct tile.
My goal here is to be able to make all the chess pieces draggable and determine if they're in their respective location. If ALL the chess pieces are in their location, it will trace or call a function.
Thank you!
import flash.events.Event;
import flash.display.MovieClip;
import flash.events.MouseEvent;
/* Declaring an X and Y variable to be used as a reset container */
var xPos: int, yPos: int;
/* Attaching event listeners for each chess piece */
addListeners(
king, queen, bishop_1, bishop_2, knight_1, knight_2, rook_1, rook_2,
pawn_1, pawn_2, pawn_3, pawn_4, pawn_5, pawn_6, pawn_7, pawn_8);
/* Getting the original x and y postion to be used as a reset */
function getPosition(currentTarget: Object): void {
xPos = currentTarget.x;
yPos = currentTarget.y;
}
/* Function to get the suffix value of an object. example, I need to get the value 4 from "pawn_4" */
function getLastCharInString($s: String, $pos: Number): String {
return $s.substr($s.length - $pos, $s.length);
}
/* A simple function that rotates the chess piece */
function lift(object: Object, rot: Number) {
object.rotation = rot;
}
function dragObject(e: MouseEvent): void {
getPosition(e.currentTarget);
lift(e.currentTarget, -10);
getChildByName(e.currentTarget.name + "_hs").alpha = 1;
e.currentTarget.startDrag();
}
/* This variable is supposed to hold the value of each piece that is correctly placed in each tile.
The total score should be 16 as there are 16 pieces. Only correcly placed piece should be added in the total score. */
var counter:int;
function stopDragObject(e: MouseEvent): void {
var curretTarget = e.currentTarget.name;
lift(e.currentTarget, 0);
/* Hide active hotspots */
getChildByName(e.currentTarget.name + "_hs").alpha = 0;
var multiplePieceSufix = Number(getLastCharInString(curretTarget, 1));
if (multiplePieceSufix >= 1) {
/* Boolean variables that checks whether the current piece is active*/
var isPawn: Boolean = false,
isBishop: Boolean = false,
isKnight: Boolean = false,
isRook: Boolean = false,
currentTargeName;
var widthDiff = getChildByName(e.currentTarget.name + "_hs").width - getChildByName(e.currentTarget.name).width / 2;
var heightDiff = getChildByName(e.currentTarget.name + "_hs").height - getChildByName(e.currentTarget.name).height / 2;
if (curretTarget.substr(0, 4) == "pawn") {
isPawn = true;
} else if (curretTarget.substr(0, 6) == "bishop") {
isBishop = true;
} else if (curretTarget.substr(0, 6) == "knight") {
isKnight = true;
} else if (curretTarget.substr(0, 4) == "rook") {
isRook = true;
}
if (isPawn == true) {
/* there are total of 8 pieces of pawn */
for (var w = 1; w < 9; w++) {
currentTargeName = this["pawn_" + w + "_hs"];
if (e.target.hitTestObject(currentTargeName)) {
/* For some reason the chess pieces are not aligning with their "_hs" version, I already checked their registry point and it seem to be normal.
so to fix, I had to manually add some hard coded values to adjust their location. */
e.currentTarget.x = currentTargeName.x - 8;
e.currentTarget.y = currentTargeName.y + currentTargeName.height;
}
}
} else if (isBishop == true) {
for (var x = 1; x < 3; x++) {
currentTargeName = this["bishop_" + x + "_hs"];
if (e.target.hitTestObject(currentTargeName)) {
e.currentTarget.x = currentTargeName.x - 9;
e.currentTarget.y = currentTargeName.y + currentTargeName.height - 18;
}
}
} else if (isKnight == true) {
for (var y = 1; y < 3; y++) {
currentTargeName = this["knight_" + y + "_hs"];
if (e.target.hitTestObject(currentTargeName)) {
e.currentTarget.x = currentTargeName.x - 8;
e.currentTarget.y = currentTargeName.y + currentTargeName.height;
}
}
} else if (isRook == true) {
for (var z = 1; z < 3; z++) {
currentTargeName = this["rook_" + z + "_hs"];
if (e.target.hitTestObject(currentTargeName)) {
e.currentTarget.x = currentTargeName.x - 8;
e.currentTarget.y = currentTargeName.y + 62;
}
}
}
} else {
if (e.target.hitTestObject(getChildByName(e.currentTarget.name + "_hs"))) {
/* Again, I'm not sure why the pieces are not aligning as intended.
modX and modY is a holder for the adjustment value. I'm not comfortable
seeing this approach myself, but I also run out of ideas how to fix it. */
var modX: Number, modY: Number;
if (e.currentTarget.name == "king") {
modX = 11;
modY = 53;
} else {
modX = 11;
modY = 29;
}
e.currentTarget.x = getChildByName(e.currentTarget.name + "_hs").x - modX;
e.currentTarget.y = getChildByName(e.currentTarget.name + "_hs").y + getChildByName(e.currentTarget.name + "_hs").height - modY;
}
}
/* This is supposed to add to the total score or count of how many pieces are placed correctly.
Thie problem with thi scounter, as it also counts any piece that is places to any "_hs" */
counter++;
trace(counter);
e.currentTarget.stopDrag();
}
function addListeners(...objects): void {
for (var i: int = 0; i < objects.length; i++) {
objects[i].addEventListener(MouseEvent.MOUSE_DOWN, dragObject);
objects[i].addEventListener(MouseEvent.MOUSE_UP, stopDragObject);
// hide hotspots
getChildByName( objects[i].name + "_hs" ).alpha = 0;
}
}
Source: Download the FLA here
--
Updates:
I have added comments in my code to clarify what I'm trying to accomplish.
I'm planning to do board game in flash which has similar function and behaviour to this. User can drag the object to a specified tile and check wether that object belongs there or not.
After reviewing your code, your question is quite broad. I'm going pair it down to what seems to be your main concern - the score / counting correctly moved pieces.
Right now, you do the following every time an object is dragged:
counter++;
This means that the counter will increment no matter where you drag the object, and no matter how times you drag the object. (so even if the piece was already in the correct spot, if you dragged it a second time it will still increment your counter).
What you need to do, is associate a flag with each object to indicate whether it is in the correct location or not, and set that flag to the appropriate value every time that object is done dragging.
Something like this:
//don't use target, use currentTarget
if (e.currentTarget.hitTestObject(currentTargeName)) {
e.currentTarget.correct = true; //since MovieClips are dynamic, you can just make up a property on them and assign a value to it.
//to fix your alignment:
e.currentTarget.x = currentTargeName.x + ((currentTargetName.width - e.currentTarget.width) * 0.5);
e.currentTarget.y = currentTargeName.y + currentTargeName.height;
}else{
//if the hit test is false, mark it as NOT correct
e.currentTarget.correct = false;
}
Then, later to know the current count, iterate over all the pieces and check their correct value. This would be much easier if all your pieces were in an array.
var allPieces:Array = [king, queen, bishop_1, bishop_2, knight_1, knight_2, rook_1, rook_2,
pawn_1, pawn_2, pawn_3, pawn_4, pawn_5, pawn_6, pawn_7, pawn_8];
function countCorrect():Boolean {
var ctr:int = 0;
for(var i:int=0;i<allPieces.length;i++){
if(allPieces[i].correct) ctr++;
}
return ctr;
}
trace(countCorrect() + " of " allPieces.length " are correct");
As an aside, this best way to do this would be with some custom class files. That would however require a complete refactoring of your code.
Also, you probably don't want to use hitTestObject, as even if a piece is mostly over a neighbor, it will still be true as long as 1 pixel of it's bound touch 1 pixel of the tile. Better would be to do a hitTestPoint on the tile, and pass in the center point of the piece (the the middle of the piece has to be touching the tile for it to count).
//a point that is the center of the events current target (the piece)
var point:Point = new Point();
point.x = e.currentTarget.x + (e.currentTarget.width * 0.5);
point.y = e.currentTarget.y - (e.currentTarget.height * 0.5);
if (currentTargetName.hitTestPoint(point)) {
EDIT 2: This problem still exists, but seems to be a bug. Adobe Sound class does not send the Sound.length value after loading a ByteArray. Here is the bug report I filed (please vote for it!):
https://bugbase.adobe.com/index.cfm?event=bug&id=3749649
= = = = =
The following code works to produce a sound once -- it plays the correct sound, but does not loop. I believe it should. I cannot seem to debug it
It also does not seem to throw a SOUND_COMPLETE event. Am I missing something here?
EDIT: Still broken, but I updated the code below so you can test it. Just copy to a class and call testSound():
private var NUM_SAMPLES:int = 16384 * 2;
private var soundByteArray:ByteArray;
private var volume:Number = 1;
private var channel:SoundChannel = new SoundChannel();
private var RATE:int = 44100;
public function testSound():void
{
var baseSound:Sound = new Sound();
storeAudio();
var trans:SoundTransform = new SoundTransform(volume, 0);
SoundMixer.soundTransform = trans;
soundByteArray.position = 0;
baseSound.loadPCMFromByteArray(soundByteArray, NUM_SAMPLES, "float", true, RATE);
soundByteArray.position = 0;
baseSound.addEventListener(flash.events.Event.SOUND_COMPLETE, onPlaybackComplete);
trace("loaded 1: " + baseSound.length);
trace("loaded 2: " + baseSound.bytesLoaded);
trace("loaded 3: " + baseSound.bytesTotal);
channel = baseSound.play(0, 20, trans);
channel.addEventListener(flash.events.Event.SOUND_COMPLETE, onPlaybackComplete);
}
protected function onPlaybackComplete(event:flash.events.Event):void
{
trace("onPlaybackComplete" + channel.position);
}
private function storeAudio():void
{
soundByteArray = new ByteArray();
for (var i:Number = 0; i < NUM_SAMPLES; i++)
{
soundByteArray.writeFloat(
Math.sin(
((i / RATE))
* Math.PI * 2 * 440
)
);
soundByteArray.writeFloat(
Math.sin(
((i / RATE))
* Math.PI * 2 * 440
)
);
}
trace("storeAudio i = " + i + ", " + soundByteArray.length);
}
OK I appreciate you've accepted this issue as a bug and possibly moved on. However I used a mini-hack to replay the loadPCMFromByteArray. Rather than rely on Sound.Complete just write code that knows when the PCM audio bytes length is fully reached.
By converting bytes length to milliseconds and using channel.position you will get essentially the same result as the .Complete feature (anyone can correct me if I'm wrong here). If you really need an event firing, i.e for the sake of some function that relies on that feedback, then you can simply despatch your own custom event and listen out for that one instead of Sound.Complete
From testing I reason that the continous glitch/click sound is actually Flash trying to play the sound forward but not moving very far into the PCM data's final bytes.
Think of it as a very audible version of an E-O-F error found in ByteArrays but then also running from an internal (never ending?) while loop just to pleasure your ears.
Some notes before code:
At measured sound ending I tried.. soundByteArray.position = 0; Not good! channel.position trace shows as stuck on the Ending pos amount. Also tried
channel = baseSound.play(0); Not good! channel.position trace shows as stuck at the zero pos. Both gave stuttering sound
Also whilst I didnt try it this time, I have looped sampleData before without glitches so I'm sure it could be worth considering copying the PCM over to a sampleData setup also and see if that works better with looping & firing a Sound.Complete etc.
If it's just simple tones you are generating & looping you don't even need to use PCMdata just go with dynamic sound generation using sampleData as first choice. If however you involve PCM samples of vocals or band music then you will need the hack below to replay on sound ending
So anyways, for now here is some code demonstration to illustrate the looping hack
package
{
import flash.display.MovieClip;
import flash.events.*;
import flash.utils.*;
import flash.media.*;
import flash.text.*;
public class testSound extends MovieClip
{
private var BIT_TYPE:int = 16; //16bit audio
private var RATE:int = 44100;
private var NUM_SAMPLES:int = 8192; //16384 * 2;
private var NUM_CHANNEL:int = 2; //if stereo
private var NUM_TEMP:int =0; //adjustable number for test without changing others
public var NUM_TONE_FREQ:int = 440;
private var soundByteArray:ByteArray;
private var volume:Number = 1;
private var channel:SoundChannel = new SoundChannel();
public var final_samples:int = 0;
public var time_total:Number; //dont use Integers here - wont always be correct
public var time_kbps:Number; //"bytes per second" count
public var loop_count:int = 0;
public var timerCount:Number = 0;
public var timerSecs:Number = 0;
public var timer:Timer;
public var trans:SoundTransform;
public var baseSound:Sound = new Sound();
public var timeText:TextField;
public var txtFormat:TextFormat;
public function testSound():void
{
//correct NUM_SAMPLES helps with end-time check
NUM_SAMPLES *= NUM_CHANNEL * BIT_TYPE;
trans = new SoundTransform(volume, 0);
channel.soundTransform = trans; //SoundMixer.soundTransform = trans;
soundByteArray = new ByteArray();
soundByteArray.position = 0;
//setup textField for debug feedback
setupTextFBack();
//generate PCM
storeAudio();
}
protected function onPlaybackComplete(event:flash.events.Event):void
{
//only works if you are passing your PCM to sampleData events,
trace("onPlaybackComplete" + channel.position);
}
private function storeAudio():void
{
for (var i:Number = 0; i < NUM_SAMPLES; i++)
{
soundByteArray.writeFloat
( Math.sin((i / RATE) * Math.PI * 2 * NUM_TONE_FREQ) );
soundByteArray.writeFloat
( Math.sin((i / RATE) * Math.PI * 2 * NUM_TONE_FREQ) );
}
trace("storeAudio samples (i) = " + i + ", ByteArray length: " + soundByteArray.length);
final_samples = i;
playAudio();
}
public function playAudio():void
{
soundByteArray.position = 0;
baseSound.loadPCMFromByteArray(soundByteArray, final_samples, "float", true, RATE);
channel = baseSound.play(); //channel = baseSound.play(0, 0, trans);
setupTimeCount(); //count when play starts
time_kbps = (RATE * NUM_CHANNEL * BIT_TYPE) / 4; //not /8 because time is never doubled on stereo
time_total = (soundByteArray.length / time_kbps);
time_total = Math.round(time_total * 100) / 100;
trace ("=== DEBUG INFO : (loop: "+loop_count+ ") =========================================");
trace ("*** Playing beyond Total Time (PCM end) creates sound glitch issues ");
trace ("*** Re-Play on a continous Tone will creates short click when it stops to replay ");
trace ("*** If PCM was music/vocals this click might not be perceived by ear if looped right");
trace ("====================================================================");
trace ("Total Kb/sec : " + time_kbps + " bytes per sec");
trace ("Total time : " + time_total + " secs" );
//trim Total millisecs just to avoid any glitches/clicks. Test & fine-tune
time_total -= 0.314; //PI divided by 10. Need fine-tune? Hell Yes!
trace ("Total (trim) : " + time_total + " secs" );
}
public function setupTimeCount():void
{
timer = new Timer(100);
timer.addEventListener(TimerEvent.TIMER, timerHandler);
timerCount = 0;
timer.start();
}
function timerHandler(Event:TimerEvent):void
{
timerCount += 100;
checkTime(timerCount);
//trace("channel.pos = " + channel.position); //for debug only
}
function checkTime(miliseconds:int) : void
{
timerSecs = miliseconds/1000;
timeText.text = ("elapsed : " + timerSecs);
//if (timerSecs >= time_total)
if ( channel.position >= (time_total * 1000) )
{
reloopAudio();
}
}
function reloopAudio():void
{
channel.stop(); //else you get stutter from going forward
timer.stop();
trace("attempting replay / loop..");
loop_count += 1;
playAudio(); //redo playing function
}
public function setupTextFBack():void
{
txtFormat = new TextFormat();
txtFormat.size = 20;
txtFormat.font = "Arial";
timeText = new TextField();
timeText.defaultTextFormat = txtFormat;
timeText.antiAliasType = AntiAliasType.ADVANCED;
timeText.x = stage.stageWidth / 2 ;
timeText.y = stage.stageHeight / 2 ;
timeText.textColor = 0xFF0000;
timeText.text = " ";
timeText.width = 200;
addChild(timeText);
}
}
}
I have a function that I call which uses a loop to create some bitmaps of spikes. Here's the bitmap variable with global scope:
public var spikes:Bitmap;
//...
And here's the function with the loop:
private function generateSpikes():void
{
for (var i:int = 0; i < 5; i++)
{
spikes = new SpikesImage();
spikes.x = (Math.random() * 500) - spikes.width;
spikes.y = (i * yDistanceBetweenSpikes) + (player.height + 300);
addChild(spikes);
}
Later in my enterFrame function, I have the statement:
spikes.x += 10;
This only moves one of the spikes bitmaps though, and I'm wanting to move all of the spikes bitmaps created within the loop. How would I go about this?
Basically, spikes is ONLY the final object set in the loop. So you need to make all of the other objects created available in another fashion. Generally, the way people do this is by storing them in an array.
private var spikeArray:Array = [];
public var spikes:Bitmap;
private function generateSpikes():void
{
for (var i:int = 0; i < 5; i++)
{
spikes = new SpikesImage();
spikes.x = (Math.random() * 500) - spikes.width;
spikes.y = (i * yDistanceBetweenSpikes) + (player.height + 300);
addChild(spikes);
spikeArray.push(spikes);
}
}
Then you can access them by looping through that array or calling a specific index of that array.
I know it was asked a thousand times before, but I still can't find a solution.
Searching SO, I indeed found the algorithm for it, but lacking the mathematical knowledge required to truly understand it, I am helplessly lost!
To start with the beginning, my goal is to compute an entire spectrogram and save it to an image in order to use it for a visualizer.
I tried using Sound.computeSpectrum, but this requires to play the sound and wait for it to end, I want to compute the spectrogram in a way shorter time than that will require to listen all the song. And I have 2 hours long mp3s.
What I am doing now is to read the bytes from a Sound object, the separate into two Vectors(.); Then using a timer, at each 100 ms I call a function (step1) where I have the implementation of the algorithm, as follows:
for each vector (each for a channel) I apply the hann function to the elements;
for each vector I nullify the imaginary part (I have a secondary vector for that)
for each vector I apply FFT
for each vector I find the magnitude for the first N / 2 elements
for each vector I convert squared magnitude to dB scale
end.
But I get only negative values, and only 30 percent of the results might be useful (in the way that the rest are identical)
I will post the code for only one channel to get rid off the "for each vector" part.
private var N:Number = 512;
private function step1() : void
{
var xReLeft:Vector.<Number> = new Vector.<Number>(N);
var xImLeft:Vector.<Number> = new Vector.<Number>(N);
var leftA:Vector.<Number> = new Vector.<Number>(N);
// getting sample range
leftA = this.channels.left.slice(step * N, step * (N) + (N));
if (leftA.length < N)
{
stepper.removeEventListener(TimerEvent.TIMER, getFreq100ms);
return;
}
else if (leftA.length == 0)
{
stepper.removeEventListener(TimerEvent.TIMER, getFreq100ms);
return;
}
var i:int;
// hann window function init
m_win = new Vector.<Number>(N);
for ( var i:int = 0; i < N; i++ )
m_win[i] = (4.0 / N) * 0.5 * (1 - Math.cos(2 * Math.PI * i / N));
// applying hann window function
for ( i = 0; i < N; i++ )
{
xReLeft[i] = m_win[i]*leftA[i];
//xReRight[i] = m_win[i]*rightA[i];
}
// nullify the imaginary part
for ( i = 0; i < N; i++ )
{
xImLeft[i] = 0.0;
//xImRight[i] = 0.0;
}
var magnitutel:Vector.<Number> = new Vector.<Number>(N);
fftl.run( xReLeft, xImLeft );
current = xReLeft;
currf = xImLeft;
for ( i = 0; i < N / 2; i++ )
{
var re:Number = xReLeft[i];
var im:Number = xImLeft[i];
magnitutel[i] = Math.sqrt(re * re + im * im);
}
const SCALE:Number = 20 / Math.LN10;
var l:uint = this.total.length;
for ( i = 0; i < N / 2; i++ )
{
magnitutel[i] = SCALE * Math.log( magnitutel[i] + Number.MIN_VALUE );
}
var bufferl:Vector.<Number> = new Vector.<Number>();
for (i = 0; i < N / 2 ; i++)
{
bufferl[i] = magnitutel[i];
}
var complete:Vector.<Vector.<Number>> = new Vector.<Vector.<Number>>();
complete[0] = bufferl;
this.total[step] = complete;
this.step++;
}
This function is executed in the event dispatched by the timer (stepper).
Obviously I do something wrong, as I said I have only negative values and further more values range between 1 and 7000 (at least).
I want to thank you in advance for any help.
With respect,
Paul
Negative dB values are OK. Just add a constant (representing your volume control) until the number of points you want to color become positive. The remaining values that stay negative are usually just displayed or colored as black in a spectrogram. No matter how negative (as they might just be the FFT's numerical noise, which can be a huge negative dB number or even NaN or -Inf for log(0)).