4

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);
}
iND
  • 2,663
  • 1
  • 16
  • 36
  • A sound 8096 samples long is pretty short (0.2 seconds), are you sure it doesn't loop, maybe you're hearing a 0.6 seconds long sound? And about handler, check if you're getting other traces, maybe you've switched compile mode to release, there are no traces. – Vesper Apr 24 '14 at 03:50
  • It doesn't matter how many times I loop, as it's always less than 1 second (I updated my answer to 1000 loops, which I have just tried). I am using FlashBuilder, so I'm certain it is the debug version, and there are other trace() results appearing. – iND Apr 24 '14 at 04:14
  • Interesting. What will happen if you set `storeAudio()` to write stereo sampled sound (two equal floats per sample) and read it as stereo sound? Also, if you'd write 8096 normal samples then say 8284 zeroes, then load 16384 samples out of the array, will that sound loop? I believe it's a kind of bug you've just hit, probably in `loadPCMFromByteArray` function. Also, try moving `baseSound` into a class-wide variable instead of locking it inside constructor - sounds are more of long-term objects than `SoundChannel`s, so it'll be better to have a sound object always accessible. – Vesper Apr 24 '14 at 09:22
  • I'll let you know the results of increasing the samples. I already tried stereo. This is only an example . . . the variables are class-level, and this is the mashup of a few methods. – iND Apr 24 '14 at 14:42
  • Same results. Actually, the sound lasts the same length of time. Something else must be wrong somewhere else in the code, but I cannot debug when it stops. – iND Apr 24 '14 at 16:46
  • Ok, I cleaned up the code in the original post, but I am not getting any repetitions. Making the `NUM_SAMPLES` variable longer only lengthens the original sound played. What am I missing?! – iND Apr 24 '14 at 17:01
  • 1
    Vesper, this looks like a bug related to the other byte-loading bugs in the `Sound` class. This one is about 0-length `Sound` after loading bytes: https://bugbase.adobe.com/index.cfm?event=bug&id=3707118 And another: https://bugbase.adobe.com/index.cfm?event=bug&id=3072682 So it looks like I have to pester Adobe about this. – iND Apr 25 '14 at 02:00
  • 1
    Your bug report link should contain a number to vote for. :) Like those you've provided above the last comment. Also as a workaround you can use `SampleDataEvent.SAMPLE_DATA` handler over your sound to directly stuff samples into your sound via the same `writeFloat()`, and once you'll write less than 2048 pairs of samples,the sound is considered completely generated and will start throwing `SOUND_COMPLETE` event after playback. – Vesper Apr 25 '14 at 03:51
  • What Vesper said... consider it. Also what would be difference between a 10 second tone vs a 1 second tone looped 10 times? I'm just thinking about if you want to loop this and yet keep a continous un-interrupted tone maybe just do a longer one to cover required "looped" timespan? – VC.One Apr 25 '14 at 19:32
  • Vesper, please see my comment on VC.one's post below about generating audio. Sorry about the bug link. . . it was very late. Here it is: https://bugbase.adobe.com/index.cfm?event=bug&id=3749649 – iND Apr 26 '14 at 01:46

1 Answers1

1

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

    }

 }
 }
VC.One
  • 14,790
  • 4
  • 25
  • 57
  • I like the code you posted, but there are two concerns for my current needs. There will be a HUGE number of `Timer`s happening at once since I am not trying to play a single sound; it's a memory concern once you get to hand-held devices. The other is something you mentioned in the text: dynamic sound generation using sampleData. This an extra listener, but also recalculates each sample played every time. PCM was a workaround for using stored data; compression is slow, so I do not want to use the `Sound.loadCompressedDataFromByteArray` method. – iND Apr 26 '14 at 01:51