iND
iND

Reputation: 2649

No looping of generated audio stored in ByteArray and loaded into Sound object?

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);
}

Upvotes: 4

Views: 260

Answers (1)

VC.One
VC.One

Reputation: 15936

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);

    }

 }
 }

Upvotes: 1

Related Questions