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)
Related
I've been working on a way to stream mic data to a server, cycle back to clients, and play back in a packet by packet manner. So far, I have the client connectivity, intercommunication, voice sending, voice receiving, buffer storage, and a broken playback. The voice coming back plays at the proper speed without scratchy noise, but it's only ever playing a % of the voice buffer, recycling, and playing the new first %. I need the client to only play sound data it retreives once (asside from resampling for proper audio speeds) and then never again.
package Voip
{
import flash.events.SampleDataEvent;
import flash.events.TimerEvent;
import flash.media.Sound;
import flash.system.System;
import flash.utils.ByteArray;
import flash.utils.Timer;
public class SoundObj
{
private var ID:int;
public var sound:Sound;
public var buf:ByteArray;
public var _vbuf:ByteArray;
public var _numSamples:int;
public var _phase:Number = 0;
public var killtimer:Timer = null;
public var _delaytimer:Timer = new Timer(1000, 1);
public function SoundObj(id:int)
{
ID = id;
buf = new ByteArray();
_vbuf = new ByteArray();
sound = new Sound();
sound.addEventListener(SampleDataEvent.SAMPLE_DATA, SoundBuffer, false, 0, true);
sound.play();
}
public function receive(bytes:ByteArray):void {
var i:int = _vbuf.position;
_vbuf.position = _vbuf.length;
_vbuf.writeBytes(bytes);
_vbuf.position = i;
_numSamples = _vbuf.length/4;
/*var i:int = buf.position;
buf.position = buf.length; // write to end
buf.writeBytes(bytes);
buf.position = i; // return to origin
if (_delaytimer == null) {
_delaytimer = new Timer(1000, 1);
_delaytimer.addEventListener(TimerEvent.TIMER, finaldata);
_delaytimer.start();
}
if (!_delaytimer.running) {
// timer not running, dump buffer and reset.
//var index:int = _vbuf.position;
//_vbuf.position = _vbuf.length;
//_vbuf.writeBytes(buf);
_vbuf = buf;
_vbuf.position = 0;
buf = new ByteArray();
//_vbuf.position = index;
//sound.extract(_vbuf, int(_vbuf.length * 44.1));
_phase = 0;
_numSamples = _vbuf.length/4;
// reset killtimer to silence timeout
killtimer = new Timer(1000, 1);
killtimer.addEventListener(TimerEvent.TIMER, killtimerEvent);
killtimer.start();
}*/
}
public function killtimerEvent(event:TimerEvent):void {
_delaytimer = null;
}
// send remaining data
public function finaldata(event:TimerEvent):void {
if (buf.length > 0) {
trace("adding final content");
//var _buf:ByteArray = new ByteArray();
//var index:int = int(_phase)*4;
//if (index >= _vbuf.length)
// index = _vbuf.position;
/*_buf.writeBytes(_vbuf, index, _vbuf.length-index);
_buf.writeBytes(buf);
buf = new ByteArray();*/
//_vbuf = _buf;
// add remaining buffer to playback
var index:int = _vbuf.position;
_vbuf.position = _vbuf.length;
_vbuf.writeBytes(buf);
_vbuf.position = index;
// wipe buffer
buf = new ByteArray();
//sound.extract(_vbuf, int(_vbuf.length * 44.1));
_phase = 0;
//_numSamples = _vbuf.length/4;
_numSamples = _vbuf.length/4;
// reset killtimer to silence timeout
killtimer = new Timer(1000, 1);
killtimer.addEventListener(TimerEvent.TIMER, killtimerEvent);
killtimer.start();
}
}
public function SoundBuffer(event:SampleDataEvent):void {
//try {
//trace("[SoundBuffer:"+ID+"]");
//sound.removeEventListener(SampleDataEvent.SAMPLE_DATA, SoundBuffer);
// buffer 4KB of data
for (var i:int = 0; i < 4096; i++)
{
var l:Number = 0;
var r:Number = 0;
if (_vbuf.length > int(_phase)*4) {
_vbuf.position = int(_phase)*4;
l = _vbuf.readFloat();
if (_vbuf.position < _vbuf.length)
r = _vbuf.readFloat();
else
r = l;
}
//if (_vbuf.position == _vbuf.length)
//_vbuf = new ByteArray();
event.data.writeFloat(l);
event.data.writeFloat(r);
_phase += (16/44.1);
if (_phase >= _numSamples) {
_phase -= _numSamples;
}
}
System.gc();
}
}
}
The initial idea was to create a SoundObj in my scene, use obj.receive(bytes) to add data to the buffer to be played back the next time the Sound player needed new data. I've been fiddling around trying to get it to work in one way or another since. The timers were designed to determine when to buffer more data, but never really worked as desired.
Proper double buffer, proper playback.
package VoipOnline
{
import flash.events.SampleDataEvent;
import flash.events.TimerEvent;
import flash.media.Sound;
import flash.system.System;
import flash.utils.ByteArray;
import flash.utils.Timer;
import flashx.textLayout.formats.Float;
public class SoundObj
{
public var ID:int;
public var sound:Sound;
internal var _readBuf:ByteArray;
internal var _writeBuf:ByteArray;
internal var n:Number;
internal var _phase:Number;
internal var _numSamples:int;
internal var myTimer:Timer;
internal var bytes:int;
public function SoundObj(id:int)
{
ID = id;
_readBuf = new ByteArray();
_writeBuf = new ByteArray();
bytes = 0;
myTimer = new Timer(10000, 0);
myTimer.addEventListener(TimerEvent.TIMER, timerHandler);
myTimer.start();
sound = new Sound();
sound.addEventListener(SampleDataEvent.SAMPLE_DATA, SoundBuffer);
sound.play();
}
public function receive(bytes:ByteArray):void
{
var i:int = _writeBuf.position;
_writeBuf.position = _writeBuf.length;
_writeBuf.writeBytes(bytes);
_writeBuf.position = i;
this.bytes += bytes.length;
}
private function timerHandler(e:TimerEvent):void{
trace((bytes/10) + " bytes per second.");
bytes = 0;
}
public function SoundBuffer(event:SampleDataEvent):void
{
//trace((_readBuf.length/8)+" in buffer, and "+(_writeBuf.length/8)+" waiting.");
for (var i:int = 0; i < 4096; i++)
{
var l:Number = 0; // silence
var r:Number = 0; // silence
if (_readBuf.length > int(_phase)*8) {
_readBuf.position = int(_phase)*8;
l = _readBuf.readFloat();
if (_readBuf.position < _readBuf.length)
r = _readBuf.readFloat();
else {
r = l;
Buffer();
}
} else {
Buffer();
}
event.data.writeFloat(l);
event.data.writeFloat(r);
_phase += 0.181;
}
}
private function Buffer():void {
// needs new data
// snip 4096 bytes
var buf:ByteArray = new ByteArray();
var len:int = (_writeBuf.length >= 4096 ? 4096 : _writeBuf.length);
buf.writeBytes(_writeBuf, 0, len);
// remove snippet
var tmp:ByteArray = new ByteArray();
tmp.writeBytes(_writeBuf, len, _writeBuf.length-len);
_writeBuf = tmp;
// plug in snippet
_readBuf = buf;
_writeBuf = new ByteArray();
_readBuf.position = 0;
_phase = 0;
}
}
}
These code snippets are based on this mic setup:
mic = Microphone.getMicrophone();
mic.addEventListener(SampleDataEvent.SAMPLE_DATA, this.micParse); // raw mic data stream handler
mic.codec = SoundCodec.SPEEX;
mic.setUseEchoSuppression(true);
mic.gain = 100;
mic.rate = 44;
mic.setSilenceLevel(voicelimit.value, 1);
After considerable testing, this seems to provide the best results so far. Little grainy, but it IS compressed and filterred. Some of the issues I'm having seem to be the fault of the server. I'm only receiving ~30% of the bytes I'm sending out. That being said, the code above works. You simply adjust the _phase increment to modify speed. (0.181 == 16/44/2) Credit will go where credit is due, even if his sample didn't quite solve the issues at hand, it was still a considerable step forward.
I have prepared some sample data and fed it into your example and got only noise sound. I have simplified your class to only two buffers one for receiving samples and second one for providing them. Hope this will work:
package {
import flash.events.*;
import flash.media.*;
import flash.utils.*;
public class SoundObj
{
private var ID:int;
public var sound:Sound;
public var _readBuf:ByteArray;
public var _writeBuf:ByteArray;
public function SoundObj(id:int)
{
ID = id;
_readBuf = new ByteArray();
_writeBuf = new ByteArray();
sound = new Sound();
sound.addEventListener(SampleDataEvent.SAMPLE_DATA, SoundBuffer);
sound.play();
}
public function receive(bytes:ByteArray):void
{
var i:int = _writeBuf.position;
_writeBuf.position = _writeBuf.length;
_writeBuf.writeBytes(bytes);
_writeBuf.position = i;
sound.play();
}
public function SoundBuffer(event:SampleDataEvent):void
{
for (var i:int = 0; i < 8192; i++)
{
if (_readBuf.position < _readBuf.length)
event.data.writeFloat(_readBuf.readFloat());
else
{
if (_writeBuf.length >= 81920)
{
_readBuf = _writeBuf;
_writeBuf = new ByteArray();
}
if (_readBuf.position < _readBuf.length)
event.data.writeFloat(_readBuf.readFloat());
else
{
//event.data.writeFloat( 0 );
}
}
}
}
}
}
// microphone sample parsing with rate change
function micParse(event:SampleDataEvent):void
{
var soundBytes:ByteArray = new ByteArray();
var i:uint = 0;
var n:Number = event.data.bytesAvailable * 44 / mic.rate * 2; // *2 for stereo
var f:Number = 0;
while(event.data.bytesAvailable)
{
i++;
var sample:Number = event.data.readFloat();
for (; f <= i; f+= mic.rate / 2 / 44)
{
soundBytes.writeFloat(sample);
}
}
snd.receive(soundBytes);
}
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 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;
}
I'm in over my head in this AS3 project.
I'm no expert in actionscript and certainly not in AS3 but I managed to create (with help from this: http://swamy-techtalk.blogspot.com/2011/07/elastic-string-to-mouse-pointer-effect.html) a eleastic string effect from a point to a draggable movieclip.
Problem is the script seems to crash flash or the browser when I test it. (Not right away just when I'm playing around with the movieclip)
Sinse I'm in over my head in the script I compiled I'm not exactly sure whats wrong.
A bit of google research hinted that it might have something to do with removeChildAt() wich I changed from removeChildAt(0) to removeChildAt(1) to prevent it from removing my movieclip.
Hope somebody has the patience to read through my script to see what I did wrong.
Example here: http://www.madsringblom.dk/flash/pullstring.html (beware it might crash your browser)
Code below:
Object(this).leaf_mc.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
var origX:int = Object(this).leaf_mc.x + 1;
var origY:int = Object(this).leaf_mc.y + 2;
var pullbackX:int = Object(this).leaf_mc.x;
var pullbackY:int = Object(this).leaf_mc.y;
var dragging:Boolean = false;
//speed of pulling and rotating back when stop dragging.
var speed:int = 15;
var addX:int = 2
var addY:int = 3
function mouseDownHandler(e:MouseEvent):void
{
var obj = e.target;
obj.startDrag();
dragging = true;
}
function mouseUpHandler(e:MouseEvent):void
{
var obj = e.target;
Object(this).leaf_mc.stopDrag();
dragging = false;
}
import flash.display.*;
import flash.events.MouseEvent;
import flash.events.Event;
var haschild:Boolean = false;
var gotonodes:Array = new Array();
var currentnodes:Array = new Array();
var posX:int = Object(this).leaf_mc.x + addX;
var posY:int = Object(this).leaf_mc.y + addY;
gotonodes = Interpolate(posX,posY,origX,origY,25);
currentnodes = gotonodes;
stage.addEventListener(Event.ENTER_FRAME, onmove1);
function onmove1(e:Event)
{
for (var node = 0; node < gotonodes.length - 1; node++)
{
currentnodes[node].xco=currentnodes[node].xco+(gotonodes[node].xco-currentnodes[node].xco)/(node*node/30+1);
currentnodes[node].yco=currentnodes[node].yco+(gotonodes[node].yco-currentnodes[node].yco)/(node*node/30+1);
}
var posX:int = Object(this).leaf_mc.x + addX;
var posY:int = Object(this).leaf_mc.y + addY;
gotonodes=Interpolate(posX,posY,origX,origY,25);
// pull leaf_mc back to starting point when released. And rotate back.
if (dragging == false)
{
Object(this).leaf_mc.x-=(Object(this).leaf_mc.x-pullbackX)/speed;
Object(this).leaf_mc.y-=(Object(this).leaf_mc.y-pullbackY)/speed;
Object(this).leaf_mc.rotation+=Object(this).leaf_mc.rotation/speed;
}
// rotating the leaf_mc according to the point (origX,origY)
var theX:int = origX - Object(this).leaf_mc.x;
var theY:int = (origY - Object(this).leaf_mc.y) * -1;
var angle = Math.atan(theY/theX)/(Math.PI/180);
Math.atan( -5 / 10) / (Math.PI / 180);
if (theX < 0)
{
angle += 180;
}
if (theX >= 0 && theY < 0)
{
angle += 360;
}
Object(this).leaf_mc.rotation = (angle*-1) + 90;
DrawNodes(currentnodes);
}
function FindAngle(x1, x2, y1, y2):Number
{
return Math.atan2(y2-y1, x2-x1);
};
function Distance(x1, x2, y1, y2):Number
{
return Math.sqrt(Math.pow(x2-x1,2)+Math.pow(y2-y1,2));
}
function Interpolate(x1, y1, x2, y2, n):Array
{
var dist= Distance(x1,x2,y1,y2);
var ang = FindAngle(x1,x2,y1,y2);
var points = [];
for (var l = 0; l <= dist; l += dist / n)
{
var x3 =x1+l*Math.cos(ang);
var y3 = y1+l*Math.sin(ang);
points.push({xco:x3,yco:y3});
}
points.push( { xco:x1, yco:y1 } );
return points;
}
function DrawNodes(array):void
{
if(haschild)
{
this.removeChildAt(1);
haschild=false;
}
var shape:Shape = new Shape();
shape.graphics.lineStyle(1,0x331100,40);
shape.graphics.moveTo(array[0].xco, array[0].yco);
for (var i = 0; i < array.length - 1; i++)
{
shape.graphics.lineTo(array[i].xco,array[i].yco);
}
shape.graphics.beginFill(0xFBFFA4,1);
shape.graphics.drawCircle(array[0].xco,array[0].yco,1);
shape.graphics.endFill();
this.addChild(shape);
haschild = true;
}
Where is this code placed? From what you pasted, I'm thinking on the stage.
A stop(); somewhere in the script might be a start and help quite a bit - otherwise the Flash movie will loop, and every time it hits this frame (every frame if you only have one), you'll add new event handlers etc. Eventually you'll run out of memory, and the onmove1 event handler will eat up your CPU, running 50 times per frame after 50 frames, 200 times after 200 frames etc.
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;
}