Remove unvanted clicks in generated sound in AS3 - actionscript-3

I am having the following problem when generating sound wave in Flash.
This is the generator part :
const SAMPLING_RATE:int = 44100;
const TWO_PI:Number = 2 * Math.PI;
const TWO_PI_OVER_SR:Number = TWO_PI / SAMPLING_RATE;
const SAMPLE_SIZE:int = 8192 / 4;
function generateSine(fq:Number):ByteArray
{
var bytes:ByteArray = new ByteArray();
var sample:Number;
var amp:Number = 1;
for (var i:int=0; i<SAMPLE_SIZE; i++)
{
sample = amp* Math.sin(i * TWO_PI_OVER_SR * fq );
bytes.writeFloat(sample);
}
bytes.position = 0;
return bytes;
}
and this is the playback part:
function playbackSampleHandler(event:SampleDataEvent):void
{
var sample:Number;
for (var i:int = 0; i < SAMPLE_SIZE && soundData.bytesAvailable; i++)
{
sample = soundData.readFloat();
event.data.writeFloat(sample);
event.data.writeFloat(sample);
}
}
function onClickPlay(e:MouseEvent)
{
soundData= new ByteArray();
var sound:ByteArray= new ByteArray();
sound=generateSine(440);
soundData.writeBytes(sound,0,sound.length);
soundData.position = 0;
morphedSound.addEventListener(SampleDataEvent.SAMPLE_DATA, playbackSampleHandler);
soundChannel = morphedSound.play();
}
Quick explanation:
I basically write values for the sine function into the ByteArray and than playback handler reads that array and plays back the sound. I write only one 8192/4 sample buffer.
Problem:
My problem is that when you do this there are annoying clicks on the end of sound, which are the artifacts of sine wave being cut on a value other than 0. So my question is how can I avoid this?
Bonus:
I would also like to know how can I generate for example exactly 100 ms of sine wave when buffers in Flash are so strict i.e. from 2048 to 8192 ?
Links:
If it helps my code is based on this tutorial
http://www.bit-101.com/blog/?p=2669
and my own explorations.

You need to preserve phase of sine wave that occurs at the end of your sample data processing. Otherwise your sine wave is not continued smoothly and you get the clicks you speak of.
var totalSamples:int=0;
function generateSine(fq:Number):ByteArray
{
var bytes:ByteArray = new ByteArray();
var sample:Number;
var amp:Number = 1;
for (var i:int=0; i<SAMPLE_SIZE; i++)
{
sample = amp* Math.sin((i+totalSamples) * TWO_PI_OVER_SR * fq );
// this uses stored phase ^^^
bytes.writeFloat(sample);
}
totalSamples+=SAMPLE_SIZE; // this preserves phase between subsequent samples
bytes.position = 0;
return bytes;
}
Update: I've noticed you are calling generator twice in a row - this is awful. You must never call a function twice if you only need one result of its work.
function onClickPlay(e:MouseEvent)
{
soundData= generateSine(440); // why using writeBytes if we can just use assignment?
soundData.position = 0;
morphedSound.addEventListener(SampleDataEvent.SAMPLE_DATA, playbackSampleHandler);
soundChannel = morphedSound.play();
}
Also, to make the sound end with a full (half-)wave of sound and remove the last click you need, use this technique:
function generateSine(fq:Number,cleanFinish:Boolean=false):ByteArray
{
var bytes:ByteArray = new ByteArray();
var sample:Number;
var amp:Number = 1;
for (var i:int=0; i<SAMPLE_SIZE; i++)
{
sample = amp* Math.sin((i+totalSamples) * TWO_PI_OVER_SR * fq );
// this uses stored phase ^^^
bytes.writeFloat(sample);
if (cleanFinish)
if ((Math.abs(sample)<(SAMPLING_RATE/fq/3))&&(i>SAMPLE_SIZE-SAMPLING_RATE/fq)) break;
// this triggers a drop if we are sampling the last wave already
}
totalSamples+=i; // this preserves phase between subsequent samples
bytes.position = 0;
return bytes;
}
Call this function with an optional Boolean parameter set to true to receive correctly terminated wave.

All SAMPLE_SIZE values of "sample" must be split in three pieces:
The first and the third piece must have 250 values each and the middle one
the rest: SAMPLE_SIZE - 500. Make this as follows:
Generate an array of 250 values between 0 and .996 with step .004 :
I mean: .000, .004, .008, ... , .996, and fill with them an array, say, mod[i]
with i from zero to 249.(mod is for modulation...)
Modify initial loop as below and so will result a fade from 0 to (+/-)amp(first 250 values) then the rest of values up to the last 250 remain (+/-)amp and, finally, the last 250 values have a fade from (+/-)amp to 0. The nasty clicks remain...history! I Hope this help.
function generateSine(fq:Number):ByteArray
{
var bytes:ByteArray = new ByteArray();
var sample:Number;
var amp:Number = 1;
for (var i:int=0; i< 250; i++)
{
sample = amp* Math.sin(i * TWO_PI_OVER_SR * fq ) * mod[i];
bytes.writeFloat(sample);
}
for (var i:int=250; i<SAMPLE_SIZE - 250; i++)
{
sample = amp* Math.sin(i * TWO_PI_OVER_SR * fq );
bytes.writeFloat(sample);
}
for (var i:int=SAMPLE_SIZE - 250; i<SAMPLE_SIZE; i++)
{
sample = amp* Math.sin(i * TWO_PI_OVER_SR * fq ) * (.996 - mod[i]);
bytes.writeFloat(sample);
}
bytes.position = 0;
return bytes;
}

Related

Need to remove a child using hit test object function

i am trying to make a function where i drop some seeds into a bucket and the seeds are removed however the function will just be the seeds colliding with the bucket.
I have both of the objects as movieclips and have a basic hitTestObject conditional statement, i have no idea why the feed_mc wont be removed.
if(bucket_mc.hitTestObject(feed_mc))
{
if(stage.contains(feed_mc))
removeChild(feed_mc);
}
thank you in advance
Sorry should have edited here
my code
var Necessities:Array = new Array (Seed, shelter, water);
for(var i:int = 0; i< 10; i++)
{
var pickObjects = Necessities[int(Math.random()* Necessities.length)];
var Objects:MovieClip = new pickObjects();
addChild(Objects);
Objects.x = Math.random() + 600;
Objects.y = Math.random() * stage.stageHeight;
}
stage.addEventListener(Event.ENTER_FRAME, feedHen);
function feedHen(e:Event):void {
if(hen_mc.hitTestObject(Objects))
{
if (Objects.parent)
Objects.parent.removeChild(Objects);
}
}
Seems like
if(feed_mc.parent){
feed_mc.parent.removeChild(feed_mc);
}
should help - if you are not sure what DisplayObjectContainer is parent
edit
I think this should work
var Necessities:Array = new Array (Seed, shelter, water);
//store Objects here
var objectsVector:Vector.<MovieClip> = new Vector.<MovieClip>();
for(var i:int = 0; i< 10; i++){
var pickObjects = Necessities[int(Math.random()* Necessities.length)];
var Objects:MovieClip = new pickObjects();
addChild(Objects);
objectsVector.push(Objects);//add to Vector
Objects.x = Math.random() + 600;
Objects.y = Math.random() * stage.stageHeight;
}
stage.addEventListener(Event.ENTER_FRAME, feedHen);
function feedHen(e:Event):void {
for(var i: int = objectsVector.length - 1; i >= 0; i--){//loop through stored objects
if(hen_mc.hitTestObject(objectsVector[i])){
if (objectsVector[i].parent){
objectsVector[i].parent.removeChild(objectsVector[i]);
objectsVector.splice(i, 1);//remove from storage
}
}
}
}
However I'd suggest checking on some mouse events instead of ENTER_FRAME to reduce number of function calls

Accessing Bitmaps created in a loop (AS3)

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.

FFT and Convolution in AS3

I am trying to load a MP3, and a HRTF file (wav) and then by Convolution to produce another output audio signal but I am a bit stuck how to do it.
sound1 = new Sound;
sound1.addEventListener (Event.COMPLETE, completeHandler);
sound1.load (new URLRequest ("test.mp3"));
channel1 = sound1.play (0, int.MAX_VALUE);
function completeHandler (event:Event):void
{ bytes1 = new ByteArray;
samples1 = new <Number> [];
fft1 = new FFT;
addEventListener (Event.ENTER_FRAME, enterFrameHandler);
}
function enterFrameHandler (event:Event):void
{ // get bytes of 512 samples
bytes1.position = 0;
sound1.extract (bytes, 1024, channel1.position * 44.1);
// get samples of left or right channel
bytes1.position = 0;
while (bytes1.bytesAvailable > 0)
{ samples1 [int (bytes1.position / 8)] = bytes1.readFloat (); bytes1.readFloat();
}
// analyze samples1
fft1.analyze (samples1);
var limitHz:Number = 5000;
var i:int=0;
var n1:int = fft1.magnitudes.length * limitHz / (44100 / 2);
// And then here I should somehow multiply the signal1*signal2?
for (i = 0; i < n1; i++)
{ var sound1Mag:int = fft1.magnitudes [i];
var sound2Mag:int = fft2.magnitudes [i];
//......?????
}
}
Any help will be much appreciated.
OK, I edited to mention that the second file is not wav but a MP3 too (I converted the wav to MP3)

AS3: Random Point on Irregular Shape

I have a MovieClip holding an irregular shape such as this one:
I need to generate a random point on this shape.
I can use brute force by generating points within the bounding box and then hitTesting to see if they reside on the irregular shape. However, I'm sure there's a more efficient way to tackle this problem.
What is the most efficient way to generate a random point on an irregular shape?
You mentioned hitTest, but I assume you meant hitTestPoint().
If so, a function go get the random points you mention, would look a bit like this:
function getRandomPointsInClip(target:MovieClip,numPoints:int):Vector.<Point>{
var points:Vector.<Point> = new Vector.<Point>(numPoints,true);
var width:Number = target.width,height:Number = target.height;
for(var i:int = 0; i < numPoints ; i++){
var point:Point = new Point(target.x+Math.random() * width,target.y+Math.random() * height);
if(target.hitTestPoint(point.x,point.y,true)) points[i] = point;//is the random coord inside ?
else i = i-1;//nope, go back one step - > retry above until it is inside
}
return points;
}
The other I hinted at in my comment involves looping through non transparent pixels in a bitmap data of your object. This method would insure you don't have many duplicates, as opposed to the previous method, but it also means, you have less control over the number of points created and there's extra memory used for creating the bitmap. Still, for documentation purposes, here is the function:
function getGridPointsInClip(target:MovieClip,res:int,offset:Number = 3):Vector.<Point>{
var points:Vector.<Point> = new Vector.<Point>();
var x:int,y:int,alpha:int,w:int = int(target.width),h:int = int(target.height);
var bmd:BitmapData = new BitmapData(w,h,true,0x00FFFFFF);bmd.draw(target);
var pixels:Vector.<uint> = bmd.getVector(bmd.rect),numPixels:int = w*h;
for(var i:int = 0; i < numPixels; i+=res) {
x = i%bmd.width;
y = int(i/bmd.width);
alpha = pixels[i] >>> 24;
if(alpha > 0) points.push(new Point(x+random(-offset,offset),y+random(-offset,offset)));
}
return points;
}
function random(from:Number,to:Number):Number {
if (from >= to) return from;
var diff:Number = to - from;
return (Math.random()*diff) + from;
}
And here'a very basic test:
var pts:Vector.<Point> = getRandomPointsInClip(mc,300);
//var pts:Vector.<Point> = getGridPointsInClip(mc,100,4);
for(var i:int = 0 ; i < pts.length; i++) drawCircle(pts[i].x,pts[i].y,3,0x009900);
function getRandomPointsInClip(target:MovieClip,numPoints:int):Vector.<Point>{
var points:Vector.<Point> = new Vector.<Point>(numPoints,true);
var width:Number = target.width,height:Number = target.height;
for(var i:int = 0; i < numPoints ; i++){
var point:Point = new Point(target.x+Math.random() * width,target.y+Math.random() * height);
if(target.hitTestPoint(point.x,point.y,true)) points[i] = point;//is the random coord inside ?
else i = i-1;//nope, go back one step - > retry above until it is inside
}
return points;
}
function getGridPointsInClip(target:MovieClip,res:int,offset:Number = 3):Vector.<Point>{
var points:Vector.<Point> = new Vector.<Point>();
var x:int,y:int,alpha:int,w:int = int(target.width),h:int = int(target.height);
var bmd:BitmapData = new BitmapData(w,h,true,0x00FFFFFF);bmd.draw(target);
var pixels:Vector.<uint> = bmd.getVector(bmd.rect),numPixels:int = w*h;
for(var i:int = 0; i < numPixels; i+=res) {
x = i%bmd.width;
y = int(i/bmd.width);
alpha = pixels[i] >>> 24;
if(alpha > 0) points.push(new Point(x+random(-offset,offset),y+random(-offset,offset)));
}
return points;
}
function random(from:Number,to:Number):Number {
if (from >= to) return from;
var diff:Number = to - from;
return (Math.random()*diff) + from;
}
function drawCircle(x:Number,y:Number,radius:Number,color:uint):void{
graphics.lineStyle(1,color);
graphics.drawCircle(x-radius,y-radius,radius);
}
HTH
If you think of some non-blob like shapes, it's clear the check random pixel, try again method isn't really a good way. The bounding box area could be huge compared to the shape area.
What you could do to improve the effectiveness is getting a vector of the BitmapData of the shape. It should contain all pixels of the bounding box. Update - it would be nice now if we could pick a random point, and remove it from the vector if it isn't inside the shape. Unfortunately the vector only contains the pixels' colour, not the position which is implicit and only correct if we don't change the vector's length. Since we don't need to know the actual colour, we can omit all transparent pixels and store an inside pixel's position as it's value in the vector. This way we don't need to create a new object for each pixel of the shape (that would be quite expensive!).
var v:Vector.<uint> shapeBoxBitmap.getVector(shapeBoxBitmap.rect);
var pixelNum:int = v.length;
for(var i:uint = 0; i < pixelNum; i++) {
if( v[i] && 0xFF000000 == 0) { // transparent pixel, outside off shape
v.splice(i,1);
} else {
v[i] = i;
}
}
//get random point
var randomPixel:int = v[Math.floor(Math.random()*v.length)];
var point:Point = new Point(randomPixel%shapeBitmap.width,int(randomPixel/shapeBitmap.width));

Intermittent/staggered loading of an object

I've just recently tried my hand at actionscript 3 and have come across a road block.
How do I go about rendering the cubes (cube1) intermittently, ie. staggered loading. I need the cubes to load a split second from each other.
Below is a snippet of what I have so far:
var rows:int = 5;
var cols:int = 3;
var spacery:int = 100;
var spacerx:int = 120;
var box_count:int = 8;
for(var i:int; i < box_count; i++) {
cube1 = new Cube(ml,100,10,80,1,1,1);
cube1.y = ((i % rows)) * (cube1.x + spacery);
cube1.x = Math.floor(i/rows) * (cube1.x +spacerx);
cube1.z = 0;
bigBox.addChild(cube1);
}
//Create an array out side the function; as a global (instance) variable:
var cubes:Array = [];
//instead of bigBox.addChild(cube1), store them in the array:
cubes.push(cube1);
//initialize a timer outside after for loop
//Fire every 100 milliseconds, box_count times
var timer:Timer = new Timer(100, box_count);
timer.addEventListener(TimerEvent.TIMER, onTick);
timer.addEventListener(TimerEvent.TIMER_COMPLETE, onTickDone);
function onTick(e:Event):void
{
bigBox.addChild(cubes[timer.currentCount]);
}
function onTickDone(e:Event):void
{
cubes = null;
timer.removeEventListener(TimerEvent.TIMER, onTick);
timer.removeEventListener(TimerEvent.TIMER_COMPLETE, onTickDone);
timer = null;
}