adireddy / haxe-howler

Externs of howler.js for Haxe
http://howlerjs.com
11 stars 7 forks source link

loop returns Howl instance instead of bool #3

Closed FlorianBT closed 8 years ago

FlorianBT commented 8 years ago

I found out that Howl.loop returns a Howl object instead of a boolean value as expected

function foo(audio:Howl, id:Int):Void
{
    trace(audio.loop(id));
}

This would log the whole Howl object. In my case, this:

 {
    _autoplay : false, 
    _ext : null, 
    _html5 : false, 
    _muted : false, 
    _loop : true, 
    _pool : 2, 
    _preload : true, 
    _rate : 1, 
    _sprite : {
        garage : [0,119328.00453514738,true], 
        harbor : [121000,31364.69387755102,true], 
        kitchen : [154000,30228.5941043084,true], 
        machine-rumble : [186000,60734.69387755102,true], 
        space-loops : [248000,11640.997732426285,true], 
        world : [261000,9176.19047619047,true]
    }, 
    _src : assets/sounds/ambient.ogg, 
    _volume : 1, 
    _duration : 272.00002267573694, 
    _loaded : true, 
    _sounds : [{
            _parent : {
                _autoplay : false, 
                _ext : null, 
                _html5 : false, 
                _muted : false, 
                _loop : true, 
                _pool : 2, 
                _preload : true, 
                _rate : 1, 
                _sprite : {
                    garage : <...>, 
                    harbor : <...>, 
                    kitchen : <...>, 
                    machine-rumble : <...>, 
                    space-loops : <...>, 
                    world : <...>
                }, 
                _src : assets/sounds/ambient.ogg, 
                _volume : 1, 
                _duration : 272.00002267573694, 
                _loaded : true, 
                _sounds : [<...>], 
                _endTimers : {
                    89891563789 : <...>
                }, 
                _onend : [<...>], 
                _onfaded : [<...>], 
                _onload : [<...>], 
                _onloaderror : [<...>], 
                _onpause : [], 
                _onplay : [], 
                _onstop : [], 
                _webAudio : true
            }, 
            _muted : false, 
            _loop : null, 
            _volume : 0.2, 
            _rate : 1, 
            _seek : 261, 
            _paused : false, 
            _ended : false, 
            _id : 89891563789, 
            _node : [object GainNode], 
            _sprite : world, 
            _start : 261, 
            _stop : 270.17619047619047, 
            _playStart : 4.289886621315193
        }], 
    _endTimers : {
        89891563789 : 12
    }, 
    _onend : [{
            fn : <function>
        }], 
    _onfaded : [{
            fn : <function>
        }], 
    _onload : [{
            fn : <function>
        }], 
    _onloaderror : [{
            fn : <function>
        }], 
    _onpause : [], 
    _onplay : [], 
    _onstop : [], 
    _webAudio : true
}

As you can see, the Howl instance itself is set to loop. Calling audio.loop() would return true. But when passing a sound id, it returns the Howl instance.

Tracking down what happens in the javascript code, my guess is that a type check "fails" in howler code.

/**
     * Get/set the loop parameter on a sound. This method can optionally take 0, 1 or 2 arguments.
     *   loop() -> Returns the group's loop value.
     *   loop(id) -> Returns the sound id's loop value.
     *   loop(loop) -> Sets the loop value for all sounds in this Howl group.
     *   loop(loop, id) -> Sets the loop value of passed sound id.
     * @return {Howl/Boolean} Returns self or current loop value.
     */
    loop: function() {
      var self = this;
      var args = arguments;
      var loop, id, sound;

      // Determine the values for loop and id.
      if (args.length === 0) {
        // Return the grou's loop value.
        return self._loop;
      } else if (args.length === 1) {
        if (typeof args[0] === 'boolean') {
          loop = args[0];
          self._loop = loop;
        } else {
          // Return this sound's loop value.
          sound = self._soundById(parseInt(args[0], 10));
          return sound ? sound._loop : false;
        }
      } else if (args.length === 2) {
        loop = args[0];
        id = parseInt(args[1], 10);
      }

      // If no id is passed, get all ID's to be looped.
      var ids = self._getSoundIds(id);
      for (var i=0; i<ids.length; i++) {
        sound = self._soundById(ids[i]);

        if (sound) {
          sound._loop = loop;
          if (self._webAudio && sound._node && sound._node.bufferSource) {
            sound._node.bufferSource.loop = loop;
          }
        }
      }

      return self;
    }

Specifically this bit of code:

else if (args.length === 1) {
        if (typeof args[0] === 'boolean') {
          loop = args[0];
          self._loop = loop;
        } else { 
...
}

The id parameter of loop() is probably converted from Int to Bool as part of the optional parameters substitution.

Thanks in advance for any quick fix :)

adireddy commented 8 years ago

Looks like this is a bug in howler. Can you log this in https://github.com/goldfire/howler.js repo.

FlorianBT commented 8 years ago

Sure! Do I present this issue linked to the use of haxe-howler? I'll do some more testing directly in JS to be sure though. I don't know if the type cast is done on Have or JS side

Le jeu. 8 oct. 2015 13:57, Adi notifications@github.com a écrit :

Looks like this is a bug in howler. Can you log this in https://github.com/goldfire/howler.js repo.

— Reply to this email directly or view it on GitHub https://github.com/adireddy/haxe-howler/issues/3#issuecomment-146516342.

adireddy commented 8 years ago

May be if you look in the generated javascript file, you can find how the parameters are casted.

Post the js file here if you can.

FlorianBT commented 8 years ago

The generated JS file is more than 2MB big in debug (HTML5 game). I would rather not post the whole file here, especially as it is for a client. Which bits interest you the most? The actual calls to howler? Or the js_Boot parts where the JS magic seems to happen?

adireddy commented 8 years ago

howler calls may help

FlorianBT commented 8 years ago

SoundMng is the base class for playing sound. It just takes a Howl instance and plays it with some tweaking parameters. It can also load a sound directly from path. Nothing magical in here, just fyi as it is the base class dealing with Howler.

$hxClasses["casuHaxeFlxEngine.SoundMng"] = casuHaxeFlxEngine_SoundMng;
casuHaxeFlxEngine_SoundMng.__name__ = ["casuHaxeFlxEngine","SoundMng"];
casuHaxeFlxEngine_SoundMng.__properties__ = {get_instance:"get_instance"}
casuHaxeFlxEngine_SoundMng.instance = null;
casuHaxeFlxEngine_SoundMng.get_instance = function() {
    return casuHaxeFlxEngine_SoundMng.instance;
};
casuHaxeFlxEngine_SoundMng.prototype = {
    load: function(Path,Volume,Looping) {
        var audio = null;
        if(Path != null && Path.length > 0) {
            var options = { };
            options.autoplay = false;
            options.preload = true;
            options.pool = 2;
            options.loop = Looping;
            options.volume = Volume;
            options.src = [Path + ".ogg",Path + ".m4a",Path + ".mp3",Path + ".ac3"];
            options.onend = function(id) {
                if(!audio.loop(id)) audio.stop(id);
            };
            options.onfaded = function(id1) {
                if(audio.volume(id1) <= 0) audio.stop(id1);
            };
            audio = new Howl(options);
        }
        return audio;
    }
    ,play: function(Sound,AudioID,Volume,Looping) {
        if(Sound != null && js_Boot.__instanceof(Sound,Howl)) {
            var soundId = Sound.play(AudioID);
            Sound.volume(Volume,soundId);
            Sound.loop(Looping,soundId);
            return soundId;
        }
        return null;
    }
    ,stop: function(Sound,AudioID,Fade,FadeDuration) {
        if(Sound != null && js_Boot.__instanceof(Sound,Howl)) {
            if(Fade && Sound.playing(AudioID)) Sound.fade(Sound.volume(AudioID),0,FadeDuration,AudioID); else Sound.stop(AudioID);
        }
    }
    ,playSound: function(Sound,AudioID,Volume,Looping) {
        if(Looping == null) Looping = false;
        if(Volume == null) Volume = 1.0;
        var sound;
        if(js_Boot.__instanceof(Sound,Howl)) sound = Sound; else sound = this.load(Sound,Volume,Looping);
        var id = this.play(sound,AudioID,Volume,Looping);
        if(sound != Sound) return sound; else return id;
    }
    ,stopSound: function(Sound,AudioID,Fade,FadeDuration) {
        if(FadeDuration == null) FadeDuration = 500;
        if(Fade == null) Fade = true;
        this.stop(Sound,AudioID,Fade,FadeDuration);
    }
    ,__class__: casuHaxeFlxEngine_SoundMng
};

SoundManagerONI is child of SoundMng. This one is the interesting part, as it loads all the sound sprites and play them. I use enum values or string values to determine which sound to play. This part works quite well, I have no issues with it, I always retrieve the correct Howl instance for my needs.

$hxClasses["onisep.SoundManagerONI"] = onisep_SoundManagerONI;
onisep_SoundManagerONI.__name__ = ["onisep","SoundManagerONI"];
onisep_SoundManagerONI.get_instance = function() {
    if(casuHaxeFlxEngine_SoundMng.get_instance() == null) return new onisep_SoundManagerONI(); else return casuHaxeFlxEngine_SoundMng.get_instance();
};
onisep_SoundManagerONI.__super__ = casuHaxeFlxEngine_SoundMng;
onisep_SoundManagerONI.prototype = $extend(casuHaxeFlxEngine_SoundMng.prototype,{
    _soundSprites: null
    ,_preloadCallback: null
    ,_preloadCount: null
    ,_preloadTotal: null
    ,loadSoundSprite: function(Path,Volume,Looping) {
        if(Looping == null) Looping = false;
        if(Volume == null) Volume = 1.0;
        this._preloadTotal++;
        haxe_Log.trace("loading " + Path,{ fileName : "SoundManagerONI.hx", lineNumber : 83, className : "onisep.SoundManagerONI", methodName : "loadSoundSprite"});
        var audio = null;
        if(Path != null && Path.length > 0) {
            var json = JSON.parse(openfl_Assets.getText(Path + ".json"));
            var options = { };
            options.autoplay = false;
            options.preload = true;
            options.pool = 2;
            options.loop = Looping;
            options.volume = Volume;
            options.src = [Path + ".ogg",Path + ".m4a",Path + ".mp3",Path + ".ac3"];
            options.sprite = json.sprite;
            options.onend = function(id) {
                if(!audio.loop(id)) audio.stop(id);
            };
            options.onfaded = function(id1) {
                if(audio.volume(id1) <= 0) audio.stop(id1);
            };
            options.onload = $bind(this,this.onSoundLoaded);
            options.onloaderror = $bind(this,this.onSoundLoaded);
            audio = new Howl(options);
        }
        return audio;
    }
    ,playSound: function(Type,AudioID,Volume,Looping) {
        if(Looping == null) Looping = false;
        if(Volume == null) Volume = 1.0;
        var audio;
        var key = this.determineType(Type);
        audio = this._soundSprites.get(key);
        if(audio == null) return null;
        return this.play(audio,this.getSoundId(AudioID),Volume,Looping);
    }
    ,stopSound: function(Type,AudioID,Fade,FadeDuration) {
        if(FadeDuration == null) FadeDuration = 500;
        if(Fade == null) Fade = true;
        var audio;
        var key = this.determineType(Type);
        audio = this._soundSprites.get(key);
        if(audio == null) return;
        this.stop(audio,AudioID,Fade,FadeDuration);
    }
    ,getSoundId: function(Name) {
        var id = null;
        switch(Type.enumIndex(Name)) {
        case 0:
            id = "clickButton";
            break;
        case 1:
            id = "click-label";
            break;
        case 2:
            id = "goodanswer";
            break;
        case 3:
            id = "badanswer";
            break;
        case 4:
            id = "success_small";
            break;
        case 5:
            id = "landing";
            break;
        case 6:
            id = "unlock";
            break;
        case 7:
            id = "open-popup";
            break;
        case 8:
            id = "fiche-perso";
            break;
        case 9:
            id = "object-found";
            break;
        case 10:
            id = "object-inventory";
            break;
        case 11:
            id = "glory";
            break;
        default:
            return Name;
        }
        return id;
    }
    ,determineType: function(T) {
        if(T != null) {
            if(js_Boot.__instanceof(T,onisep_SoundType)) return T; else if(typeof(T) == "string") return Type.createEnum(onisep_SoundType,T);
        }
        return null;
    }
    ,setPreloadCallback: function(Callback) {
        this._preloadCallback = Callback;
    }
    ,onSoundLoaded: function() {
        this._preloadCount++;
        if(this._preloadCallback != null) this._preloadCallback(this._preloadCount / this._preloadTotal);
    }
    ,__class__: onisep_SoundManagerONI
});

The issue I found is located here, in an event callback (onend callback)

var audio = null;
        if(Path != null && Path.length > 0) {
            var json = JSON.parse(openfl_Assets.getText(Path + ".json"));
            var options = { };
            options.autoplay = false;
            options.preload = true;
            options.pool = 2;
            options.loop = Looping;
            options.volume = Volume;
            options.src = [Path + ".ogg",Path + ".m4a",Path + ".mp3",Path + ".ac3"];
            options.sprite = json.sprite;
            options.onend = function(id) {
                if(!audio.loop(id)) audio.stop(id);
            };
            options.onfaded = function(id1) {
                if(audio.volume(id1) <= 0) audio.stop(id1);
            };
            options.onload = $bind(this,this.onSoundLoaded);
            options.onloaderror = $bind(this,this.onSoundLoaded);
            audio = new Howl(options);
        }
        return audio;

In Howl.hx, onend is Int->Void, so id should be an Int. Therefore audio.loop(id) should return a boolean.

Does it help?

adireddy commented 8 years ago

yes, got it now... I think I know the issue. Will do a quick test and then update the externs.

adireddy commented 8 years ago

Fixed it @FlorianBT. Please check and let me know. v2.0.1 http://lib.haxe.org/p/howlerjs/2.0.1

Also updated the sample to trace the return value of loop function call and its returning Bool. http://adireddy.github.io/demos/haxe-howler/ Play sound 1 or sound 2 and see the traces in console.

FlorianBT commented 8 years ago

Hello again.

Sadly, my project doesn't compile anymore with your latest changes.

SoundManagerONI.hx:107: characters 7-28 : Cannot compare howler.Howl and Int
SoundMng.hx:86: characters 15-36 : howler.Howl should be Float
SoundMng.hx:86: characters 15-36 : For function argument 'from'
SoundMng.hx:46: characters 7-28 : Cannot compare howler.Howl and Int

Involved lines in Haxe, in order:

SoundManagerONI.hx:107: characters 7-28 : Cannot compare howler.Howl and Int

options.onfaded = function(id:Int):Void {
    //trace("faded " + id + " volume : " + audio.volume(id));
===>    if(audio.volume(id) <= 0)
            audio.stop(id);
        };
};
SoundMng.hx:86: characters 15-36 : howler.Howl should be Float
SoundMng.hx:86: characters 15-36 : For function argument 'from'

if(Fade && Sound.playing(AudioID)) 
{
===>    Sound.fade(Sound.volume(AudioID), 0, FadeDuration, AudioID);
} 
SoundMng.hx:46: characters 7-28 : Cannot compare howler.Howl and Int

options.onfaded = function(id:Int):Void {
===>    if(audio.volume(id) <= 0)
            audio.stop(id);
        };
};

I looked a bit at your new overloads and did some researches as I couldn't find a reason it selected the wrong overload. I found this : http://blog.onthewings.net/2012/07/13/haxe-tips-advanced-method-overloading-with-macros/ Maybe we're in the same case here.

adireddy commented 8 years ago

I see the problem and it's really annoying... anyway as a quick fix I changed the return type to Dynamic to fix the issue.

Can you try again and let me know. v2.0.2

I did a quick test and it's fine.

FlorianBT commented 8 years ago

Tested, compiling and working as expected. Thanks a lot for your time!

adireddy commented 8 years ago

:+1: