Get the total length of samples of audio in Flash - actionscript-3

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..

Related

How to STOP looping sound when going into next frame/Errors

I have a flash project broken up into multiple frames, with a button on each frame that goes to play the next frame. (And a movieclip on each frame that plays until you hit next frame button)
On each frame, I want audio to play, and loop.
But, I want the audio from one frame to stop when I click the button to go to the next.
On frame 4, I have this code:
import flash.media.SoundChannel;
var sound:Sound = new firt2();
var soundChannel:SoundChannel;
sound.addEventListener(Event.COMPLETE, onSoundLoadComplete);
sound.play();
function onSoundLoadComplete(e:Event):void{
sound.removeEventListener(Event.COMPLETE, onSoundLoadComplete);
soundChannel = sound.play();
soundChannel.addEventListener(Event.SOUND_COMPLETE, onSoundChannelSoundComplete);
}
function onSoundChannelSoundComplete(e:Event):void{
e.currentTarget.removeEventListener(Event.SOUND_COMPLETE, onSoundChannelSoundComplete);
}
And it works. However, I want to stop it once I click the button to go to the next frame. I have tried:
soundChannel.stop();
On the next frame.
However, whenever I do that, the output reads:
TypeError: Error #1009: Cannot access a property or method of a null object reference.
at hhh4_fla::MainTimeline/frame5()
at flash.display::MovieClip/gotoAndPlay()
at hhh4_fla::MainTimeline/fl_ClickToGoToAndPlayFromFrame()
All of my buttons and movieclip have instance names.
Rather than figuring why it doesn't work with all these frames and timelines, I think it's better to compose a centralized sound manager class that handles these things.
Implementation. Keep in mind that I didn't test that so please excuse me for occasional typo if any. The logic of it all should be correct.
package
{
import flash.system.ApplicationDomain;
import flash.media.SoundChannel;
import flash.media.Sound;
import flash.events.Event;
public class Audio
{
// Container to cache Sound objects.
static private const cache:Object = new Object;
// Variables to hold the current values.
static private var currentChannel:SoundChannel;
static private var currentSound:String;
// Stops the current sound playing. If you pass the sound name, it
// will stop the audio track only if it is the exact one playing.
// Otherwise it will stop any one currently playing.
static public function stop(value:String = null):void
{
// Do nothing if nothing is playing right now,
// or if the specific sound requested to stop does not match.
if (currentSound == null) return;
if (value) if (value != currentSound) return;
// Unsubscribe from event and stop the audio.
currentChannel.removeEventListener(Event.SOUND_COMPLETE, onComplete);
currentChannel.stop();
// Final clean-up.
currentChannel = null;
currentSound = null;
}
// Plays the embedded sound by its class name.
static public function play(value:String):void
{
// Do nothing if the requested sound is already playing.
if (value == currentSound) return;
// Stop the current audio track playing.
stop();
// Check if that one sound is valid and/or was previously requested.
if (!cache[value])
{
try
{
// Obtain class definition from the project.
var aClass:Class = ApplicationDomain.currentDomain.getDefinition(value) as Class;
// Try instantiating the Sound.
if (aClass) cache[value] = new aClass as Sound;
}
catch (fail:Error)
{
// Well, do nothing, yet.
}
}
if (cache[value])
{
// Store the id of audio track that is going to be playing.
currentSound = value;
// Play the track and subscribe to it for the SOUND_COMPLETE event.
currentChannel = (cache[value] as Sound).play();
currentChannel.addEventListener(Event.SOUND_COMPLETE, onComplete);
}
else
{
// If there's no such class, or it is not a Sound,
trace("ERROR: there's no sound <<" + value + ">> is embedded into the project.");
}
}
// Event handler to clean up once the current audio track is complete.
static private function onComplete(e:Event):void
{
// Sanity check.
if (e.target != currentChannel) return;
stop();
}
}
}
Usage.
import Audio;
// Any time you want different sound to play.
// Pass the class name as Sting as an argument.
Audio.play("firt2");
// Any time you just want to stop the sound;
Audio.stop();

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.

SampleDataEvent.SAMPLE_DATA - regulating how often this event is tirggered

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.

AS3 playing audio and looping it

I'm trying to get an audio file to play in the background of my project but so far have been unsuccessful so far. Heres what I've gotten so far.
package {
import flash.display.*;
import flash.events.*;
import flash.net.*;
import flash.media.*;
public class Music extends MovieClip {
// Create a sound
private var music:Sound;
// Use a URLRequest for file path
private var request:URLRequest;
// Create a sound buffer
private var buffer:SoundLoaderContext;
// Create a sound channel
private var channel:SoundChannel;
public function Music() {
// constructor code
//Instantiate all sound objects
buffer = new SoundLoaderContext(1000);
request = new URLRequest("SMB3-Grassland-Map.mp3");
music = new Sound(request, buffer);
// Play music and assign it to a channel
channel = music.play();
// Add an event listener to the channel
channel.addEventListener(Event.SOUND_COMPLETE, loopMusic);
}
// playMusic method restarts music
private function playMusic()
{
channel = music.play();
channel.addEventListener(Event.SOUND_COMPLETE, loopMusic);
}
// Channel event listener call playMusic method
private function loopMusic(e:Event)
{
if (channel != null)
{
channel.removeEventListener(Event.SOUND_COMPLETE, loopMusic);
playMusic();
}
}
}
}
this is just to play an external audio file and have it continually loop.
Rather than having an event listener that plays the sound again upon sound complete, you can add parameters to the .play() method. The first parameter indicates at what point in the sound you would like it to begin playing, the second parameter indicates how many times you would like it to play, and the third is used if you want to apply sound transform. In your case you can do .play(0, int.MAX_VALUE); this will give you the continuous loop you are looking for.
"the problem is it's not playing anything at all."
I copied your code and created an AS3 class file (substituting my own local MP3 URL)
Then created an AS3 file with the following:
import Music;
var mus:Music = new Music();
and ran it. The sound played and looped correctly.
It's working for me, so maybe that's why there are no errors. Sorry for the question but are your speakers on; is your system playing sound from another source... cd, mp3 player?
I've used the play(0, 1000); #Goose mentioned and it worked great; simple and effective.

Determining when audio is ready to play

I am currently playing an audio file progressively like this:
import flash.media.Sound;
import flash.media.SoundLoaderContext;
import flash.net.URLRequest;
var s:Sound = new Sound();
var context:SoundLoaderContext = new SoundLoaderContext(5000, false);
s.load(new URLRequest('AUDIO URL'), context);
s.play();
which works great, but I want to know when it has enough data (based on the buffer time) and starts playing the file. To clarify, I am not trying to determine when the file has completely downloaded, I know that is done with Event.COMPLETE
How about using the ProgressEvent?
s.addEventListener(ProgressEvent.PROGRESS, progressHandler);
function progressHandler(event:ProgressEvent):void {
trace( event.bytesLoaded );
}
So when the bytesloaded is equal to the buffer amount, you know it.
Edit:
Perhaps you could check for the isBuffering attribute to see if the buffer is filled or not.