Sound synthesis: interpolate betweeen frequencies using AS3 - actionscript-3

I'm a little lost and hopefully someone can shed some light on this.
Out of curiosity I'm working on a simple softsynth/sequencer. Some ideas
were taken from the .mod format popular in the golden era of home computers.
At the moment it's just a mock-up. Notes are read out from an array holding
up to 64 values, where each position in the array corresponds to a sixteenth
note. So far so good, everything's working as it should and the melody plays
just fine. The problem arises if there's a transition from one note to another.
e.g. f4 -> g#4. Since this is an abrupt change there's a noticeable pop/click
sound. To compensate I'm trying to interpolate between different frequencies
and started to code a simple example to illustrate my idea and verify it's
working.
import flash.display.Sprite;
import flash.events.Event;
import flash.display.Bitmap;
import flash.display.BitmapData;
public class Main extends Sprite
{
private var sampleRate:int = 44100;
private var oldFreq:Number = 349.1941058508811;
private var newFreq:Number = 349.1941058508811;
private var volume:Number = 15;
private var position:int = 0;
private var bmp:Bitmap = new Bitmap();
private var bmpData:BitmapData = new BitmapData(400, 100, false, 0x000000);
private var col:uint = 0xff0000;
public function Main():void
{
if (stage)
init();
else
addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
bmp.bitmapData = bmpData;
addChild(bmp);
for (var a:int = 0; a < 280; a++)
{
if (a == 140)
{
col = 0x00ff00;
newFreq = 415.26411519488113;
}
if (a == 180)
{
col = 0x0000ff;
}
oldFreq = oldFreq * 0.9 + newFreq * 0.1;
bmpData.setPixel(position, Math.sin((position) * Math.PI * 2 / sampleRate * oldFreq * 2) * volume + bmpData.height/2, col);
position++;
}
}
}
This will generate the following output:
The blue dots represent a sine wave at 349.1941058508811 hz, the red 415.26411519488113 hz and the green dots the interpolation.
For my eyes, it looks like this should work!
If I apply this technique to my project however, the result isn't the same!
In fact, if I render the output to a wave file, the transition between those
two frequencies looks like this:
Obviously it makes the popping even worse. What could possibly be wrong?
Here's my (shortened )code:
import flash.display.*;
import flash.events.Event;
import flash.events.*;
import flash.utils.ByteArray;
import flash.media.*;
import flash.utils.getTimer;
public class Main extends Sprite
{
private var sampleRate:int = 44100;
private var bufferSize:int = 8192;
private var bpm:int = 125;
private var numberOfRows:int = 64;
private var currentRow:int = 0;
private var quarterNoteLength:Number;
private var sixteenthNoteLength:Number;
private var numOctaves:int = 8;
private var patterns:Array = new Array();
private var currentPattern:int;
private var songOrder:Array = new Array();
private var notes:Array = new Array("c-", "c#", "d-", "d#", "e-", "f-", "f#", "g-", "g#", "a-", "a#", "b-");
private var frequencies:Array = new Array();
private var samplePosition:Number = 0;
private var position:int = 0;
private var channel1:Object = new Object();
public function Main():void
{
if (stage)
init();
else
addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
quarterNoteLength = sampleRate * 60 / bpm;
sixteenthNoteLength = quarterNoteLength / 2 / 2;
for (var a:int = 0; a < numOctaves; a++)
{
for (var b:int = 0; b < notes.length; b++)
{
frequencies.push(new Array(notes[b % notes.length] + a, 16.35 * Math.pow(2, frequencies.length / 12)));
}
}
patterns.push(new Array("f-4", "", "", "", "g#4", "", "", "f-4", "", "f-4", "a#4", "", "f-4", "", "d#4", "", "f-4", "", "", "", "c-5", "", "", "f-4", "", "f-4", "c#5", "", "c-5", "", "g#4", "", "f-4", "", "c-5", "", "f-5", "", "f-4", "d#4", "", "d#4", "c-4", "", "g-4", "", "f-4", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""));
songOrder = new Array(0, 0);
currentRow = 0;
currentPattern = 0;
channel1.volume = .05;
channel1.waveform = "sine";
channel1.frequency = [0];
channel1.oldFrequency = [0,0,0,0];
channel1.noteTriggered = false;
updateRow();
var sound:Sound = new Sound();
sound.addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData);
sound.play();
}
private function updateRow():void
{
var tempNote:String = patterns[songOrder[currentPattern]][currentRow];
if (tempNote != "")
{
channel1.frequency = new Array();
if (tempNote.indexOf("|") == -1)
{
channel1.frequency.push(findFrequency(tempNote));
}
channel1.noteTriggered = true;
}
}
private function onSampleData(event:SampleDataEvent):void
{
var sampleData:Number;
for (var i:int = 0; i < bufferSize; i++)
{
if (++samplePosition == sixteenthNoteLength)
{
if (++currentRow == numberOfRows)
{
currentRow = 0;
if (++currentPattern == songOrder.length)
{
currentPattern = 0;
}
}
updateRow();
samplePosition = 0;
}
for (var a:int = 0; a < (channel1.frequency).length; a++ )
{
channel1.oldFrequency[a] = channel1.oldFrequency[a]*0.9+channel1.frequency[a]*0.1;
}
if ((channel1.frequency).length == 1)
{
sampleData = generate(channel1.waveform, position, channel1.oldFrequency[0], channel1.volume);
}
else
{
sampleData = generate(channel1.waveform, position, channel1.oldFrequency[0], channel1.volume);
sampleData += generate(channel1.waveform, position, channel1.oldFrequency[1], channel1.volume);
}
event.data.writeFloat(sampleData);
event.data.writeFloat(sampleData);
position++;
}
}
private function generate(waveForm:String, pos:Number, frequency:Number, volume:Number):Number
{
var retVal:Number
switch (waveForm)
{
case "square":
retVal = Math.sin((pos) * 2 * Math.PI / sampleRate * frequency) > 0 ? volume : -volume;
break;
case "sine":
retVal = Math.sin((pos) * Math.PI * 2 / sampleRate * frequency * 2) * volume;
break;
case "sawtooth":
retVal = (2 * (pos % (sampleRate / frequency)) / (sampleRate / frequency) - 1) * volume;
break;
}
return retVal;
}
private function findFrequency(inpNote:String):Number
{
var retVal:Number;
for (var a:int = 0; a < frequencies.length; a++)
{
if (frequencies[a][0] == inpNote)
{
retVal = frequencies[a][1];
break;
}
}
return retVal;
}
}
Thanks! =)

You miss that when you switch frequencies, pos value in generate loses invariance, that is, Math.sin((pos) * Math.PI * 2 / sampleRate * frequency * 2) gives VERY different values when ran with different frequencies. Instead, you should use "phase" variable that would run from 0 to 1 then back to 0 and forward again like a sawtooth diagram, and will be forwarded by the value of (current frequency)*(1/sampling rate). So the error is where you add two generate() results to one sampleData (you plain cannot do that because the interference) and where you use one position as a time value to calculate phase instead of accumulated phase. Check this approach, it should work a tad better:
private function generate(waveForm:String, var phase:Number, frequency:Number, volume:Number):Number {
// "pos" changed to "phase". This also means that "generate" should be called once per sample
var retVal:Number;
switch (waveForm)
{
case "square":
retVal = Math.sin(phase * 2 * Math.PI) > 0 ? volume : -volume;
break;
case "sine":
retVal = Math.sin(phase * 2 * Math.PI ) * volume;
break;
case "sawtooth":
retVal = (2*Math.abs(2*phase-1)-1)* volume;
break;
}
phase+=frequency/sampleRate;// calculate new phase
if (phase>1.0) { phase-=1.0; } // normalize phase to 0..1
return retVal;
}
private function onSampleData(event:SampleDataEvent):void {
var sampleData:Number;
for(var i:int=0;i<bufferSize;i++) {
if (++samplePosition == sixteenthNoteLength)
{ // leaving this part as is, seems working
if (++currentRow == numberOfRows)
{
currentRow = 0;
if (++currentPattern == songOrder.length)
{
currentPattern = 0;
}
}
updateRow();
samplePosition = 0;
}
sampleData=0;
for (i=0;i</*channels.length*/1;i++) {
// TODO convert "channel1" to an array
// sampleData+=generate(channels[i].waveform, channels[i].phase, channels[i].frequency, channels[i].volume);
sampleData+=generate(channel1.waveform, channel1.phase, channel1.frequency[0], channel1.volume);
}
event.data.writeFloat(sampleData);
event.data.writeFloat(sampleData);
}
}
In fact, your channels should go to a separate class that will hold all the parameters together (phase, freq, waveform, volume), then, whenever you'd need them to sample, you just could call channels[i].generateNextSample() and get a float without all the hassle with parameters. Also, one channel, one frequency, so skip those "oldFrequency" stuff.
As a follow-up, a sketch for Channel class:
public class Channel {
public const WAVE_SINE:int=0;
public const WAVE_SQUARE:int=1;
public const WAVE_SAWTOOTH:int=2;
private var phase:Number=0;
private var currentVolume:Number=0;
public var volume:Number; // 0 to 1, should build a setter to normalize
public var frequency:Number=0;
public var waveform:int; // should also not allow changing this mid-play probably
public function Channel(v:Number=0,wf:int=WAVE_SINE,f:Number=0) {
this.volume=v;
this.frequency=f;
this.waveform=wf;
phase=0;
currentVolume=0;
}
public function generateNextSample():Number {...} // use the generate() code above to fill
public function reset():void { currentVolume=0; phase=0; } // POW
// rest to taste, enabled, active, whatever
}
An example of use:
var ch:Vector.<Channel>=new Vector.<Channel>();
ch.push(new Channel());
function onSampleData(e:SampleDataEvent):void {
for (var j:int=0;j<8192;j++) {
// here to input code that can alter channels' freqs, volumes etc
var sd:Number=0;
for (var i:int=ch.length-1; i>=0;i--) { sd+=ch[i].generateNextSample(); }
e.data.writeFloat(sd);
e.data.writeFloat(sd);
}
}

Related

Error: Call to a possibly undefined method crearNotaS through a reference with static type Class

I'm new at programming and I'm working on a 'minigame' for my career.
I'm getting this error, hope someone can help me out.
public class Notas
{
public var stage:Stage;
public var velocidad:int = 5;
public var i:int = 0;
public var notaS:Array = new Array(16);
public var notaD:Array = new Array(16);
public function Notas(escenario:Stage)
{
stage = escenario;
}
public function Inicializar():void
{
crearNotaS(0xFFFFFF, 30, 10, 0, 0);
}
public function Destruir():void
{
if (notaS[i].y < 720)
{
for (i = 0; i < notaS.length; i++)
{
stage.removeChild(notaS[i])
}
}
}
public function Mover():void
{
notaS[i].y += velocidad;
}
public function drawRect(color:uint, ancho:int, alto:int, x:int, y:int):Sprite
{
var dj:Sprite = new Sprite();
dj.graphics.beginFill(color,1);
dj.graphics.drawRect(0,0,ancho,alto);
dj.graphics.endFill();
dj.x = x;
dj.y = y;
return(dj);
}
public function asignarNotas():void
{
notaS[0] = 1
}
public function crearNotaS(color:int, ancho:int, alto:int, x:int, y:int):void
{
var contador:int = 0;
for (i = 0; i < notaS.length; i++)
{
if (notaS[i] == 1 && i == 0)
{
notaS[i] = drawRect(color, ancho, alto, x, y);
stage.addChild(notaS[i]);
notaS[i].y = -alto / 2;
}
else if (notaS[i] == 1 && i > 0)
{
for (j = i; j < notaS.length; j++)
{
contador = i - j
notaS[i] = drawRect(color, ancho, alto, x, notaS[j].y + alto * contador);
stage.addChild(notaS[i]);
return;
}
}
}
}
}
Its supposed to create a array of squares (only if the content of the index is a 1, if it is a 0 then it won't create the square there.) one on top of each other and then move them all down, like a guitar hero.
Probably the way im doing it isn't proper but well, its the 1st thing i'm doing on my own...
ActionScript is an object oriented Language. Classes are supposed to be objects too and when you want to access their methods you either need to make an instance of them first or make sure the target function is of type "Static", which has limitations of its own.
This is all about core concepts which you need to know before running your code. I suggest taking a look at some tutorials about classes. This might be a good start:
Tutorial: Understanding Classes in AS3 Part 1
But about your code. It misses some imports and variable definitions, not a major flaw though. Im able to run your code. I will attach a zip file containing the AS3 AIR project which i created to test the code: Test Project
You could do this yourself in couple of steps:
Create an As3 project in the IDE of your choice. (I use FlashDevelop)
Declare the variable of type YourClass (Notas) which holds instance of your class
before using functions of your class make an instance of your class and store it in the variable.
viola, use public methods and properties of your class by accessing the variable you created.
this is the main function:
package
{
import flash.display.Sprite;
/**
* ...
* #author tkiafar
*/
public class Main extends Sprite
{
private var _not:Notas;
public function Main():void
{
if (stage) {
_not = new Notas(this.stage);
_not.asignarNotas();
_not.Inicializar();
}
}
}
}
this is your class that resides in the main package (beside main.as):
package
{
import flash.display.Sprite;
import flash.display.Stage;
public class Notas
{
public var stage:Stage;
public var velocidad:int = 5;
public var i:int = 0;
public var j:int = 0;
public var notaS:Array = new Array(16);
public var notaD:Array = new Array(16);
public function Notas(escenario:Stage)
{
stage = escenario;
}
public function Inicializar():void
{
crearNotaS(0xFF00FF, 30, 10, 0, 0);
}
public function Destruir():void
{
if (notaS[i].y < 720)
{
for (i = 0; i < notaS.length; i++)
{
stage.removeChild(notaS[i])
}
}
}
public function Mover():void
{
notaS[i].y += velocidad;
}
public function drawRect(color:uint, ancho:int, alto:int, x:int, y:int):Sprite
{
var dj:Sprite = new Sprite();
dj.graphics.beginFill(color, 1);
dj.graphics.drawRect(0, 0, ancho, alto);
dj.graphics.endFill();
dj.x = x;
dj.y = y;
return (dj);
}
public function asignarNotas():void
{
notaS[0] = 1
}
public function crearNotaS(color:int, ancho:int, alto:int, x:int, y:int):void
{
var contador:int = 0;
for (i = 0; i < notaS.length; i++)
{
if (notaS[i] == 1 && i == 0)
{
notaS[i] = drawRect(color, ancho, alto, x, y);
stage.addChild(notaS[i]);
notaS[i].y = -alto / 2;
}
else if (notaS[i] == 1 && i > 0)
{
for (j = i; j < notaS.length; j++)
{
contador = i - j
notaS[i] = drawRect(color, ancho, alto, x, notaS[j].y + alto * contador);
stage.addChild(notaS[i]);
return;
}
}
}
}
}
}
after all i have some suggestions:
seperate the array that contains control indexes and the one that contains sprites.
avoid making variables public. in your case there is no need for accessing them outside your class, but if you need to make them accessible, use getters/setters. google "getters/setters in as3".
invent some naming standards of your own. google "rules of naming in as3".

Actionscript 3.0 - MouseEvents not working

I'm trying to code a sort of strategy game through FlashDevelop and I'm getting problems when trying to use Events (particularly MouseEvents). It's not so much that the events are returning errors, it's just that they don't do anything, not even getting a trace.
I'm trying to make the hexagons HexObject image invisible when clicked on (just a simple test to see if the MouseEvent is actually working).
This is my code:
Main.as
package {
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
/**
* ...
* #author Dean Sinclair
*/
public class Main extends Sprite {
public var gameInitialised:Boolean = false;
public var MIN_X:int = 0;
public var MAX_X:int = stage.stageWidth;
public var MIN_Y:int = 0;
public var MAX_Y:int = stage.stageHeight - 100;
public var GameGrid:HexGrid = new HexGrid(MIN_X, MAX_X, MIN_Y, MAX_Y);
public var blackBG:Shape = new Shape();
public function Main():void {
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
addEventListener(Event.ENTER_FRAME, update);
// entry point
}
private function update(event:Event):void {
if (gameInitialised == false) {
GameGrid.initialiseGrid();
initialiseBackground();
gameInitialised = true;
}
updateGraphics();
}
public function drawGrid():void {
for (var x:int = 0; x < GameGrid.TOTAL_X; x++) {
for (var y:int = GameGrid.yArray[x][0]; y < GameGrid.yArray[x][1]; y++) {
if (x != GameGrid.nox || y != GameGrid.noy) {
GameGrid.Grid[x][y].update();
this.stage.addChild(GameGrid.Grid[x][y].image);
}
}
}
}
public function updateGraphics():void {
this.stage.addChild(blackBG);
drawGrid();
}
public function initialiseBackground():void {
blackBG.graphics.beginFill(0x000000, 1);
blackBG.graphics.lineStyle(10, 0xffffff, 1);
blackBG.graphics.drawRect(0, 0, stage.stageWidth-1, stage.stageHeight-1);
blackBG.graphics.endFill();
}
}
}
HexGrid.as
package {
import flash.display.Sprite;
import flash.events.Event;
/**
* ...
* #author Dean Sinclair
*/
public class HexGrid extends Sprite {
public static var HEX_RADIUS:int = 0;
public static var HEX_DIAMETER:int = 0;
public static var GRID_WIDTH:int = 0;
public static var GRID_HEIGHT:int = 0;
public var TOTAL_X:int = 0;
public var MIN_X:int = 0;
public var MAX_X:int = 0;
public var MIN_Y:int = 0;
public var MAX_Y:int = 0;
public var Grid:Array;
public var yArray:Array;
public function HexGrid(min_x:int, max_x:int, min_y:int, max_y:int) {
super();
MIN_X = min_x;
MAX_X = max_x;
MIN_Y = min_y;
MAX_Y = max_y;
}
public function initialiseGrid():void {
setGridDetails();
setLineLengths();
setGridPositions();
}
public function setGridDetails():void {
HEX_RADIUS = 25;
HEX_DIAMETER = 2 * HEX_RADIUS;
GRID_WIDTH = (((MAX_X - MIN_X) / HEX_DIAMETER) - 1);
GRID_HEIGHT = ((((MAX_Y - 100) - MIN_Y) / (HEX_DIAMETER - (HEX_DIAMETER / 3))) - 3);
TOTAL_X = GRID_WIDTH + Math.floor((GRID_HEIGHT - 1) / 2);
}
private function setLineLengths():void {
yArray = new Array(TOTAL_X);
for (var a:int = 0; a < TOTAL_X; a++) {
yArray[a] = new Array(2);
}
for (var x:int = 0; x < TOTAL_X; x++) {
if (x < GRID_WIDTH) {
yArray[x][0] = 0;
}else {
yArray[x][0] = (x - GRID_WIDTH + 1) * 2;
}
yArray[x][1] = 1 + (2 * x);
if (yArray[x][1] > GRID_HEIGHT) {
yArray[x][1] = GRID_HEIGHT;
}
trace("Line", x, " starts at", yArray[x][0], " ends at", yArray[x][1]);
}
}
public var nox:int = 5;
public var noy:int = 3;
private function setGridPositions():void {
var hexState:int = 4;
Grid = new Array(TOTAL_X);
for (var x:int = 0; x < TOTAL_X; x++) {
Grid[x] = new Array(yArray[x][1]);
for (var y:int = yArray[x][0]; y < yArray[x][1]; y++) {
if(nox!=4 || noy!=6){
Grid[x][y] = new HexObject(HEX_DIAMETER + (HEX_DIAMETER * x) - (HEX_RADIUS * y), HEX_DIAMETER + (HEX_DIAMETER * y) - ((HEX_DIAMETER / 3) * y), HEX_RADIUS, 2);
}
}
}
}
}
}
HexObject.as
package {
import flash.display.Bitmap;
import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
/**
* ...
* #author Dean Sinclair
*/
public class HexObject extends Sprite {
[Embed(source = "../images/hexagons/hex_darkRed.png")]
private var DarkRedHex:Class;
[Embed(source = "../images/hexagons/hex_lightBlue.png")]
private var LightBlueHex:Class;
[Embed(source = "../images/hexagons/hex_midGreen.png")]
private var MidGreenHex:Class;
public var image:Bitmap = new LightBlueHex();
protected var state:int = 0;
private var radius:int = 0;
public function HexObject(xPos:int, yPos:int, hexRadius:int, hexState:int) {
super();
x = xPos;
y = yPos;
state = hexState;
radius = hexRadius;
checkState();
initialiseGraphics();
}
private function checkState():void {
switch(state) {
case 1: // plains
image = new MidGreenHex();
break;
case 2: // hills
break;
case 3: // rock
image = new DarkRedHex();
break;
case 4: // water
image = new LightBlueHex();
break;
default:
break;
}
}
private function initialiseGraphics():void {
image.visible = true;
image.width = radius * 2;
image.height = radius * 2;
image.x = x - radius;
image.y = y - radius;
}
private function onMouseClick(e:MouseEvent):void {
image.visible = false;
trace("image.visible =", image.visible);
}
public function update():void {
image.addEventListener(MouseEvent.CLICK, onMouseClick);
}
}
}
I've tried countless methods to get the events working, but none have had any success. Any sort of solution to this would be a lifesaver as I've been toiling over this for hours, thanks!
My problem was fixed by VBCPP, I was using the class Bitmap which cannot dispatch MouseEvents. The solution was to take image from HexObject and put it inside an container of type Sprite, with the logical one being the object it was in. I just had to add the following code inside HexObject.as:
this.addChild(image);
and then just refer to the Object in future as opposed to image.

Code for a 10x10 multiplication table in flash as3,

I'm new to coding in AS3, and I would like some assistance. I've recieved a task where I have to code a Multiplication table (10 rows, 10 columns). I've managed to code the table, but I need help to add the numbers needed without manually adding the text.
It should look somewhat like this;
Can someone here assist me with this task?
Here is my code:
var xColumns:uint=10;
var xRows:uint=10;
var _columnWidth:Number=40;
var _rowHeight:Number=40
var _width:Number=_columnWidth*xColumns;
var _height:Number=_rowHeight*xRows;
graphics.lineStyle(2, 0x0000ff, 1);
for(var i:int=1; i<=xColumns; i++){
graphics.moveTo(i*_columnWidth,0);
graphics.lineTo(i*_columnWidth,_height);
}
for(i=1; i<=xRows; i++){
graphics.moveTo(0,i*_rowHeight);
graphics.lineTo(_width,i*_rowHeight);
}
This is the sample of "Cell" class:
package
{
import flash.display.Graphics;
import flash.display.Sprite;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
public class Cell extends Sprite
{
//---------------------------------------
protected var m_oLabel:TextField;
protected var m_nWidth:Number = 20;
protected var m_nHeight:Number = 20;
protected var _backgroundColor:Number;
//---------------------------------------
//Group: CONSTRUCTOR
public function Cell()
{
super();
m_oLabel = new TextField();
m_oLabel.text = "999";
m_oLabel.autoSize = TextFieldAutoSize.CENTER;
addChild(m_oLabel);
setLabel("999");
}
//--------------------------------------------------//
// PUBLIC PUBLIC PUBLIC PUBLIC //
//--------------------------------------------------//
public function setLabel(p_sLabel:String):void
{
m_oLabel.text = p_sLabel || "";
updateLayout();
}
public function get backgroundColor():Number
{
return _backgroundColor;
}
public function set backgroundColor(value:Number):void
{
_backgroundColor = value;
redraw();
}
//------------------- OVERRIDDEN -------------------//
override public function get width():Number
{
return m_nWidth;
}
override public function set width(value:Number):void
{
m_nWidth = value;
updateLayout();
redraw();
}
override public function get height():Number
{
return m_nHeight;
}
override public function set height(value:Number):void
{
m_nHeight = value;
updateLayout();
redraw();
}
//--------------------------------------------------//
// PRIVATE/PROTECTED PRIVATE/PROTECTED //
//--------------------------------------------------//
protected function updateLayout():void
{
//adjust layout
if (m_oLabel)
{
m_oLabel.x = width * .5 - m_oLabel.width * .5;
m_oLabel.y = height * .5 - m_oLabel.height * .5;
}
}
protected function redraw():void
{
var g:Graphics = this.graphics;
g.beginFill(_backgroundColor);
g.drawRect(0, 0, width, height);
g.endFill();
}
//------------------- OVERRIDDEN -------------------//
//--------------------------------------------------//
// EVENTS EVENTS EVENTS EVENTS //
//--------------------------------------------------//
//------------------- OVERRIDDEN -------------------//
//--------------------------------------------------//
// UTILS UTILS UTILS UTILS //
//--------------------------------------------------//
//------------------- OVERRIDDEN -------------------//
}
}
and usage:
var container:Sprite = new Sprite();
addChild(container);
container.x = 10;
container.y = 10;
for (var i:int = 0; i <= 10; i++)
{
for (var j:int = 0; j <= 10; j++)
{
var cell:Cell = new Cell();
if (i == 0 || j == 0)
{
//headers
cell.setLabel(Math.max(i, j).toString());
cell.backgroundColor = 0x808080;
}
else
{
var mul:Number = i * j;
cell.setLabel(mul.toString());
cell.backgroundColor = 0xC0C0C0;
}
container.addChild(cell);
cell.x = cell.width * j;
cell.y = cell.height * i;
}
}
which looks like this:
new class "Cell" gives you flexibility and encapsulation of code for creation of single table cell. In my example it is only container for textfield and has ability to draw background. I've created public methods/accessors that allow to modify the "features" of the cell (i.e. what is displayed in TextField and what is colour of background). I had to override default width/height behaviour as otherwise it would return the size of it's content which would vary depending on the TextField content and size and in table we need steady size.

User interaction with Leapmotion AS3 library

I can connect the device and attach a custom cursor to one finger, but I can´t use any of the gestures to over/click a button or drag a sprite around, etc.
I´m using Starling in the project. To run this sample just create a Main.as, setup it with Starling and call this class.
My basic code:
package
{
import com.leapmotion.leap.Controller;
import com.leapmotion.leap.events.LeapEvent;
import com.leapmotion.leap.Finger;
import com.leapmotion.leap.Frame;
import com.leapmotion.leap.Gesture;
import com.leapmotion.leap.Hand;
import com.leapmotion.leap.InteractionBox;
import com.leapmotion.leap.Pointable;
import com.leapmotion.leap.ScreenTapGesture;
import com.leapmotion.leap.Vector3;
import starling.display.Shape;
import starling.display.Sprite;
import starling.events.Event;
import starling.events.TouchEvent;
/**
* ...
* #author miau
*/
public class LeapController extends Sprite
{
private var _controller:Controller;
private var _cursor:Shape;
private var _screenTap:ScreenTapGesture;
private var _displayWidth:uint = 800;
private var _displayHeight:uint = 600;
public function LeapController()
{
addEventListener(Event.ADDED_TO_STAGE, _startController);
}
private function _startController(e:Event):void
{
removeEventListener(Event.ADDED_TO_STAGE, _startController);
//adding controller
_controller = new Controller();
_controller.addEventListener( LeapEvent.LEAPMOTION_INIT, onInit );
_controller.addEventListener( LeapEvent.LEAPMOTION_CONNECTED, onConnect );
_controller.addEventListener( LeapEvent.LEAPMOTION_DISCONNECTED, onDisconnect );
_controller.addEventListener( LeapEvent.LEAPMOTION_EXIT, onExit );
_controller.addEventListener( LeapEvent.LEAPMOTION_FRAME, onFrame );
//add test button
_testButton.x = stage.stageWidth / 2 - _testButton.width / 2;
_testButton.y = stage.stageHeight / 2 - _testButton.height / 2;
addChild(_testButton);
_testButton.touchable = true;
_testButton.addEventListener(TouchEvent.TOUCH, doSomething);
//draw ellipse as a cursor
_cursor = new Shape();
_cursor.graphics.lineStyle(6, 0xFFE24F);
_cursor.graphics.drawEllipse(0, 0, 80, 80);
addChild(_cursor);
}
private function onFrame(e:LeapEvent):void
{
trace("ON FRAME STARTED");
var frame:Frame = e.frame;
var interactionBox:InteractionBox = frame.interactionBox;
// Get the first hand
if(frame.hands.length > 0){
var hand:Hand = frame.hands[0];
var numpointables:int = e.frame.pointables.length;
var pointablesArray:Array = new Array();
if(frame.pointables.length > 0 && frame.pointables.length < 2){
//trace("number of pointables: "+frame.pointables[0]);
for(var j:int = 0; j < frame.pointables.length; j++){
//var pointer:DisplayObject = pointablesArray[j];
if(j < numpointables){
var pointable:Pointable = frame.pointables[j];
var normal:Vector3 = pointable.tipPosition;
var normalized:Vector3 = interactionBox.normalizePoint(normal);
//pointable.isFinger = true;
_cursor.x = normalized.x * _displayWidth;
_cursor.y = _displayHeight - (normalized.y * _displayHeight);
_cursor.visible = true;
}else if (j == 0) {
_cursor.visible = false;
}
}
}
}
}
private function onExit(e:LeapEvent):void
{
trace("ON EXIT STARTED");
}
private function onDisconnect(e:LeapEvent):void
{
trace("ON DISCONNECT STARTED");
}
private function onConnect(e:LeapEvent):void
{
trace("ON CONNECT STARTED");
_controller.enableGesture( Gesture.TYPE_SWIPE );
_controller.enableGesture( Gesture.TYPE_CIRCLE );
_controller.enableGesture( Gesture.TYPE_SCREEN_TAP );
_controller.enableGesture( Gesture.TYPE_KEY_TAP );
}
private function onInit(e:LeapEvent):void
{
trace("ON INIT STARTED");
}
private function doSomething(e:TouchEvent):void
{
trace("I WAS TOUCHED!!!");
}
}
}
If a good code Samaritan can update this code to perform a screen tap gesture (or any interacion with any object), I will really appreciate this a lot.
Regards!
controller.enableGesture(Gesture.TYPE_SWIPE);
controller.enableGesture(Gesture.TYPE_SCREEN_TAP);
if(controller.config().setFloat("Gesture.Swipe.MinLength", 200.0) && controller.config().setFloat("Gesture.Swipe.MinVelocity", 500)) controller.config().save();
if(controller.config().setFloat("Gesture.ScreenTap.MinForwardVelocity", 30.0) && controller.config().setFloat("Gesture.ScreenTap.HistorySeconds", .5) && controller.config().setFloat("Gesture.ScreenTap.MinDistance", 1.0)) controller.config().save();
//etc...
Then catch it in the frame event listener:
private function onFrame( event:LeapEvent ):void
{
var frame:Frame = event.frame;
var gestures:Vector.<Gesture> = frame.gestures();
for ( var i:int = 0; i < gestures.length; i++ )
{
var gesture:Gesture = gestures[ i ];
switch ( gesture.type )
{
case Gesture.TYPE_SCREEN_TAP:
var screentap:ScreenTapGesture = ScreenTapGesture ( gesture);
trace ("ScreenTapGesture-> x: " + Math.round(screentap.position.x ) + ", y: "+ Math.round( screentap.position.y));
break;
case Gesture.TYPE_SWIPE:
var screenSwipe:SwipeGesture = SwipeGesture(gesture);
if(gesture.state == Gesture.STATE_START) {
//
}
else if(gesture.state == Gesture.STATE_STOP) {
//
trace("SwipeGesture-> direction: "+screenSwipe.direction + ", duration: " + screenSwipe.duration);
}
break;
default:
trace( "Unknown gesture type." )
}
}
}
When the event occurs, check the coordinates translated to the stage/screen and whether a hit test returns true.
EDIT: Considering I have no idea how to reliable get the touch point x/y (or better: how to translate them to the correct screen coordinates), I would probably do something like this in my onFrame event:
private function onFrame(event:LeapEvent):void {
var frame:Frame = event.frame;
var gestures:Vector.<Gesture> = frame.gestures();
var posX:Number;
var posY:Number;
var s:Shape;
if(frame.pointables.length > 0) {
var currentVector:Vector3 = screen.intersectPointable(frame.pointables[0], true); //get normalized vector
posX = 1920 * currentVector.x - stage.x; //NOTE: I hardcoded the screen res value, you can get it like var w:int = leap.locatedScreens()[0].widthPixels();
posY = 1080 * ( 1 - currentVector.y ) - stage.y; //NOTE: I hardcoded the screen res value, you can get it like var h:int = leap.locatedScreens()[0].heightPixels();
}
for(var i:int = 0; i < gestures.length; i++) {
var gesture:Gesture = gestures[i];
if(gesture.type == Gesture.TYPE_SCREEN_TAP) {
if(posX >= _button1.x &&
posX <= _button1.x + _button1.width &&
posY >= _button1.y &&
posY <= _button1.y + _button1.height) {
s = new Shape();
s.graphics.beginFill(0x00FF00);
s.graphics.drawCircle(0, 0, 10);
s.graphics.endFill();
s.x = posX;
s.y = posY;
stage.addChild(s);
trace("Lisa tocada!");
}
else {
s = new Shape();
s.graphics.beginFill(0xFF0000);
s.graphics.drawCircle(0, 0, 10);
s.graphics.endFill();
s.x = posX;
s.y = posY;
stage.addChild(s);
trace("Fallaste! Intentalo otra vez, tiempo: "+new Date().getTime());
}
}
}
}

how to make Astar pathfinding can used by multi enemy?

i have learn A star pathfinding from "AdvancED_ActionScript_Animation" e-book:
List item
i make node class, Node.as .
i make grid class, Grid.as .
i make AStar class, AStar.as .
and the main clas, Game.as .
A-star Pathfinding is work, but just 1 player.
how to make it work in multi player?
spoiler
4.Game.as :
package
{
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.MouseEvent;
public class Game extends Sprite
{
private var _cellSize:int = 30;
private var _grid:Grid;
private var _player:Sprite;
private var _index:int;
private var _path:Array;
public function Game()
{
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
makePlayer();
makeGrid();
stage.addEventListener(MouseEvent.CLICK, onGridClick);
}
private function makePlayer():void
{
_player =new Sprite();
_player.graphics.beginFill(0x336633);
_player.graphics.drawCircle(0,0,5);
_player.graphics.endFill();
_player.x = Math.random() * 100;
_player.y = Math.random() * 100;
addChild(_player);
}
private function makeGrid():void
{
_grid = new Grid(10,10);
for (var i:int =0; i < 10; i++)
{
_grid.setWalkable(Math.floor(Math.random()*10),
Math.floor(Math.random()*10),false);
}
drawGrid();
}
private function drawGrid():void
{
graphics.clear();
for (var i:int =0; i < _grid.numCols; i++)
{
for (var j:int =0; j<_grid.numRows; j++)
{
var node:Node = _grid.getNode(i,j);
graphics.lineStyle(0);
graphics.beginFill(getColor(node));
graphics.drawRect(i *_cellSize,
j*_cellSize,_cellSize,
_cellSize);
}
}
}
private function getColor(node:Node):uint
{
if (! node.walkable)
{
return 0;
}
if (node == _grid.startNode)
{
return 0xcccccc;
}
if (node == _grid.endNode)
{
return 0xcccccc;
}
return 0xffffff;
}
private function onGridClick(event:MouseEvent):void
{
var xpos:int =Math.floor(mouseX/_cellSize);
var ypos:int =Math.floor(mouseY/_cellSize);
_grid.setEndNode(xpos,ypos);
xpos = Math.floor(_player.x / _cellSize);
ypos = Math.floor(_player.y / _cellSize);
_grid.setStartNode(xpos,ypos);
drawGrid();
findPath();
}
private function findPath():void
{
var astar:AStar = new AStar();
if (astar.findPath(_grid))
{
_path = astar.path;
_index = 0;
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
}
private function onEnterFrame(event:Event):void
{
var targetX:Number = _path[_index].x * _cellSize
+ _cellSize / 2;
var targetY:Number = _path[_index].y * _cellSize
+ _cellSize / 2;
var dx:Number = targetX - _player.x;
var dy:Number = targetY - _player.y;
var dx2:Number = targetX - _box.x;
var dy2:Number = targetY - _box.y;
var dist2:Number =Math.sqrt(dx2*dx2+dy2+dy2);
var dist:Number =Math.sqrt(dx*dx+dy+dy);
if (dist<1)
{
_index++;
if (_index >= _path.length)
{
removeEventListener(Event.ENTER_FRAME,onEnterFrame);
}
}
else
{
_player.x += dx * .5;
_player.y += dy * .5;
}
}
}
}
I am not going to go the extra mile and code this for you. (Anyone else can feel free to do that)
What you need to do is create a function that accepts a start node and end node, and returns the path. Something like this :
public function getPath(starNode:Point, endNode:Point):Array
{
// get your path by utilizing the astar instance
// return that path array
// note that I have used Points to store the x,y location
// you'll need to apply those values to the grid before your astar call
// using the setStarNode and setEndNode methods of the grid instance
}
Then you can modify the code that is used in your enterFrame, to utilize an array containing path data for any player/enemy you want.
For example you might have a function you call for each entity such as :
public function followPath(entity:MovieClip, pathData:Array, pathIndex:int):void
{
// your modified code from onEnterFrame
// replace _player variable with entity
// replace _path variable with pathData
// replace _index with pathIndex
}
If you understand OOP well enough, you might want to create a class for your player/enemy entities that has properties for pathData and pathIndex. If you do that, than you can just call an update method that will update their movement each frame using the pathData and pathIndex properties of that instance.
This is a basic approach you could use to accomplish your goal.