SampleDataEvent.SAMPLE_DATA - regulating how often this event is tirggered - actionscript-3

I is to possible to regulate how often flash triggers SAMPLE_DATA event when sampling data from input device ? I need to sample constantly (even silence).
var mySound:Sound = new Sound();
mySound.addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData);
AFAIK currently this function gets called 20 times/s

playing with the sample code from http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/events/SampleDataEvent.html
var mySound:Sound = new Sound();
function sineWaveGenerator(event:SampleDataEvent):void {
for ( var c:int=0; c<8192; c++ ) {
event.data.writeFloat(Math.sin((Number(c+event.position)/Math.PI/2))*0.25);
event.data.writeFloat(Math.sin((Number(c+event.position)/Math.PI/2))*0.25);
}
}
mySound.addEventListener(SampleDataEvent.SAMPLE_DATA,sineWaveGenerator);
mySound.play();
the less data you write, the more often the method gets called.
if you call it too often the sound may get clicky and gross. try adjusting the frame rate.

Related

Flash SoundChannel limit and memory management

I have a program that I use as a recording platform, and for each sound I play I make a new Sound.
public function playAudioFile():void {
trace("audio file:", currentAudioFile);
sound = new Sound();
sound.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
sound.addEventListener(Event.COMPLETE, soundLoaded);
sound.load(new URLRequest(soundLocation + currentAudioFile));
}
public function soundLoaded(event:Event):void {
sound.removeEventListener(IOErrorEvent.IO_ERROR, errorHandler);
sound.removeEventListener(Event.COMPLETE, soundLoaded);
var soundChannel:SoundChannel = new SoundChannel();
soundChannel = sound.play();
soundChannel.addEventListener(Event.SOUND_COMPLETE, handleSoundComplete);
trace('soundChannel?', soundChannel);
}
public function handleSoundComplete(event:Event):void {
var soundChannel:SoundChannel = event.target as SoundChannel;
soundChannel.stop();
soundChannel.removeEventListener(Event.SOUND_COMPLETE,handleSoundComplete);
soundChannel = null;
}
After 32 times, I stop getting a SoundChannel object when I call sound.play() (in soundLoaded). However, I don't need to have 32 SoundChannel objects because I play these sounds only serially and not at the same time. How can I get rid of the SoundChannel after I 'used' it to play a file?
You could be explicit about the soundChannel you use :
var soundChannel:SoundChannel = new SoundChannel();
soundChannel = sound.play();
soundChannel.addEventListener(Event.SOUND_COMPLETE,handleSoundComplete);
Then when you are done playing the sound, you can then set the channel to null so it will be marked for garbage collection :
function handleSoundComplete(e:Event):void
{
var soundChannel:SoundChannel = e.target as SoundChannel;
soundChannel.removeEventListener(Event.SOUND_COMPLETE,handleSoundComplete);
soundChannel = null;
}
Keep in mind that you need to remove those event listeners that you show in your code above, when the sound is done loading.
Also keep in mind that when you set something to null, that just sets it as fair game for garbage collection, it doesn't force garbage collection.
Another note is that this code I have posted is just an example, and you might want to think about having several sound channels instances that you keep active, and reuse as needed. Management is required in that case, but then you will not be constantly creating/killing sound channels.

Sounds play delay wrong

I am writing simple metronome component using Flex + AS3. I want it playing 'tick1' sound after, for example, each 500 millisecond and each 4th time plays another sound 'tick2'. But actually delay between sound is not equivalent - sometimes lesser, sometimes greater a bit. I testing it on latest Chrome.
Here my code:
//Somewhere here button bound to the 'toggle' function
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.media.SoundTransform;
import flash.media.SoundChannel;
private var bpm:Number = 120; //2 bit per second, delay=500ms
private var period:Number = 4;
private var timer:Timer = new Timer(bpm, period);
[Embed(source='sounds/1.mp3')]
private var tickSound1Class:Class;
private var tickSound1:Sound;
[Embed(source='sounds/2.mp3')]
private var tickSound2Class:Class;
private var tickSound2:Sound;
private var trans:SoundTransform = new SoundTransform(1);
private function init():void {
....
tickSound1 = new tickSound1Class() as Sound;
tickSound2 = new tickSound2Class() as Sound;
update();
timer.addEventListener(TimerEvent.TIMER, onTimerEvent);
....
}
private function update():void {
timer.delay = 1000 * 60/bpm;
timer.repeatCount = 0;
}
private function toggle():void {
if (timer.running) {
timer.reset();
startStopButton.label = "Start";
} else {
update();
timer.start();
startStopButton.label = "Stop";
}
}
private function onTimerEvent(event:TimerEvent):void {
var t:Timer = event.currentTarget as Timer;
if (t.currentCount % period == 0)
tickSound1.play(0, 0, trans);
else
tickSound2.play(0, 0, trans);
}
I think there are two main reasons:
It is known that Timer object in Flash Player is inaccurate, delay between it's fires fluctuates.
Sound.play() method also introduces some delay before the sound actually starts to play, and theoretically this delay can fluctuate. The delay is especially noticeable in PPAPI version of Flash Player being used in Chrome.
There are several solutions. I would suggest one of these:
Use pre-composed sound of the whole metronome cycle (tick1-pause1-tick2-pause2) and just loop it using the second argument of Sound.play() method;
use dynamic sound generation.
The second option is more flexible but is more difficult to implement. Basicaly, you'll need to create a new instance of Sound object, subscribe to it's SAMPLE_DATA event and call it's play() method. In the handler you'll check event.position / 44.1, which will give you current position of sound generation in ms. Then, if you decide that it's time to play tick1 or tick2 sound, you'll call tickN.extract(event.data, ...), where tickN is tick1 or tick2 Sound object, or write the silence otherwise.
You can read more about dynamic sound generation here.
Also, notice that when you call Sound.play() it returns a SoundChannel object, which has the position property. Is it a position in ms of a sound that is being played (not generated) at the moment, and it is accurate. So, using this property, you can come up with the third approach: create a Sound object and setup a SAMPLE_DATA handler like in the dynamic sound generation solution, but write the silence (zeros) to the event.data object inside the handler all the time. This is needed to obtain a sound channel without actually playing a sound. Then, use high frame rate (60 FPS) and a Timer with the smallest possible delay (1 ms). Each time the Timer fires, check soundChannel.position to determine whether it is time to play the tick sound, and, if it so, just play it like you're doing in your example. This approach is likely to solve the problem of the Timer inaccuracy, but it cannot deal with the delay caused by tickSound.play() method.

Get the total length of samples of audio in Flash

I'm having a lot of trouble getting the total sample length of an mp3.
Also, when I load in the mp3 it does not play all the way through. It plays about half of it's length.
The mp3 I'm testing can be found here, http://bit.ly/GYSOYj.
The code I'm using to play the sound is:
[Embed(source='assets/ffmangun_loop_79393.mp3')]
private var _snd_class:Class;
private var sound:Sound = new _snd_class as Sound;
protected function application1_creationCompleteHandler(event:FlexEvent):void {
sound.length; // 600;
sound.bytesTotal;// 44652
soundChannel = sound.play();
soundChannel.addEventListener(Event.SOUND_COMPLETE, soundComplete);
}
Update:
It was suggested that I load the MP3 instead of embed it. The result is the MP3 plays the full length and the length and the bytes total are now different numbers.
// embedded values
bytes total = 44652
sound length = 600.8163265306122
// load dynamic values
bytes total = 44678
sound length = 1776.3265306122448
Update
This needs to be determined at runtime.
Firstly, "total samples" != sound.bytesLoaded != sound.length. They are all quite different.
In the code below I've included some other Event listeners to try to illustrated the differences. Listening for 'Event.SOUND_COMPLETE' won't tell you much, first off because it'll only fire when the sound has stopped playing, and secondly because it's been proven to be very unreliable. Using Event.OPEN also doesn't tell you much, as the file hasn't started loading yet, and no info can be retrieved or calculated.
You can either use a ProgressEvent or an EnterFrame event to monitor the loading of the Sound and read the length property, but to determine the total samples you'll also need to know what sample rate was used to create the MP3 and whether it's stereo or mono. Once you know these things, you can easily get the sample length of the file by multiplying the length by the sample rate.
Hopefully this usage example helps -
import flash.net.URLRequest;
import flash.events.Event;
import flash.media.Sound;
import flash.media.SoundChannel;
var sound:Sound;
var soundChannel:SoundChannel;
var sampleRate:Number = 44.1;
var totalSamples:int;
init();
function init( ):void {
sound = new Sound();
sound.load( new URLRequest( "http://bit.ly/GYSOYj") );
sound.addEventListener( Event.OPEN, soundOpen );
sound.addEventListener( Event.COMPLETE, soundComplete );
soundChannel = sound.play();
soundChannel.addEventListener(Event.SOUND_COMPLETE, scComplete);
}
function soundOpen( e:Event ):void{
trace( "OPEN:", sound.length );
}
function soundComplete( e:Event ):void{
trace( "COMPLETE:", sound.length );
totalSamples = sound.length * sampleRate;
trace( "SAMPLES", totalSamples);
}
function scComplete( e:Event):void{
trace( "sound is complete" );
}
edit: as suggested, one could use as3swf ( bit.ly/HdhVcW ) - but only you can decide if it's worth it or not. Can't imagine what you are trying to build that would require all this..

AS3 Delay execution?

I'm querying Rotten Tomatoes' API for movies listed in an XML document. The problem I'm having is, whist iterating through and querying for each movie I'm hitting RT's API limit which, in turn, is throwing an IO error.
Is there a way I can delay execution in a loop for about a second or so, to avoid this?
Short answer: use a Timer.
Suppose you have 100 movies in an array:
var movies:Array;
And for each of these movies you want to call an API on the server. First I would maintain a variable to mark the current movie:
var currentMovieIndex:int = 0; // first movie
Then, assuming you're using URLLoader:
private function makeAPICall():void
{
// Construct URLRequest based on current movie object.
var urlRequest:URLRequest = getURLRequest(movies[currentMovieIndex]);
var urlLoader:URLLoader = new URLLoader();
urlLoader.addEventListener("complete", completeHandler);
urlLoader.load(urlRequest);
}
getURLRequest() being a function that gives you a URLRequest object for the movie you pass to it as an argument.
In your completeHandler():
private function completeHandler(event:Event):void
{
...
if (++currentMovieIndex < 100) {
var timer:Timer = new Timer(3000, 1); // 3-second delay
timer.addEventListener("timer", timerHandler);
timer.start();
}
}
In your timerHandler():
private function timerHandler(event:Event):void
{
makeAPICall();
}
What are you loading from the xml that is causing the error? XML should not take long to load. If your loading items contained in the xml then a timer will not be helpful. It'll take too long and give a bad experience. Say the user has a fast connection but still has to wait on a delay for each file. What I'd do is check to make sure I've loaded one file(loadBytes) before moving to the next file. You'd only have to do this with the largest file contained in each node should be enough.
That would be more flexibly dependent on the users connection.

Why do MovieClipLoader events not fire when loaded into an AS3 wrapper?

While trying to answer this question: Call to an AS2 function from the AS3 container I have come across a roadblock. The setup is an AS3 SWF which loads an AS2 SWF, which in turn loads another AS2 SWF. Communication between the AS3 SWF and the parent AS2 SWF is achieved through localConnection.
child_as2.swf - This is a very simple timeline animation of a box moving across the screen with the following code on frame 1:
stop();
function playMovie() {
play();
}
parent_as2.swf - This is the intermediary AS2 container which loads in child_as2.swf. The load is triggered by a LocalConnection call:
import mx.utils.Delegate;
this._lockroot = true;
var container:MovieClip = createEmptyMovieClip("container", 10);
//mustn't cast this or the Delegate breaks
var mcLoader = new MovieClipLoader();
mcLoader._lockroot = true;
mcLoader.onLoadInit = Delegate.create(this,onMCLoadInit);
function onMCLoadInit() {
trace("load init");
container.playMovie();
}
//LocalConnection code
var myLC:LocalConnection = new LocalConnection();
myLC.loadChild = function(){
mcLoader.loadClip("child_as2.swf", container);
trace("loading");
}
myLC.connect("AVM");
parent_as3.swf - This is the outer wrapper, written in AS3. It loads parent_as2.swf, and communicates with it via LocalConnection:
var myLC:LocalConnection = new LocalConnection();
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.INIT,onLoaded);
loader.load(new URLRequest("parent_as2.swf"));
addChild(loader);
function onLoaded(event:Event):void {
//setTimeout hack to circumvent #2000 Security context error
setTimeout(function() {
myLC.send("AVM", "loadChild");
},1);
}
The issue is that the onMCLoadInit function in parent_as2 is never called when it is loaded inside the AS3 wrapper, although the load does take place. The events also fail when using a listener object in place of Delegate. The box from child_as2.swf is visible, but never starts moving. However, if I run parent_as2.swf on it's own and start the load without the LocalConnection it works fine. It also wroks correctly when triggered from an external LocalConnection call. Why does the AS3 wrapper prevent the MovieClipLoader's events from firing?
Update:
So accepting that no events can be fired from the MovieClipLoader in parent_as2.swf, I have modified the code to detect the loadInit state by a combination of polling MovieClipLoader.getProgress() and the existance of a function in child_as2.swf. It's not pretty but it seems to work. I would still much rather be able to offer a solution using events though.
var container:MovieClip = createEmptyMovieClip("container", 10);
var mcLoader:MovieClipLoader = new MovieClipLoader();
var loadStarted:Boolean;
var checkingInt:Number;
function checkProgress() {
var progObj:Object = mcLoader.getProgress(container);
if(progObj.bytesLoaded == progObj.bytesTotal && loadStarted) {
//load complete, wait for loadInit
if(typeof(container.playMovie) == "function") {
//loadInit
clearInterval(checkingInt);
container.playMovie();
}
}
//ensures the first loop is ignored due to inaccuracy with reporting
loadStarted = true;
}
//LocalConnection code
var myLC:LocalConnection = new LocalConnection();
myLC.loadChild = function() {
loadStarted = false;
mcLoader.loadClip("child_as2.swf", container);
checkingInt = setInterval(checkProgress,5);
}
myLC.connect("AVM");
I think its the delegate scope in as2 when loaded into a parent the parent becomes _root.
So "this" would be referring to the parent root where the function does not exist.
Have you tried putting this._lockroot = true; in parent_as2?
Here is a better explanation
Also as a side note to your security hack.
The proper fix for that would be to have the child contact the parent and issue an "I am ready type command" which would start the communication events from parent to child.
setTimeout is just delaying any calls to the child giving it time to initialize which could be bad on slower computers.
I did alot of loading AS2 into AS3 a few years ago. If you can't tell lol